/git-secure
Encrypt folders and files in a git repo so they're plaintext locally but encrypted on GitHub. Uses git-crypt for transparent encryption and age for versioned snapshots.
When to Use
Run /git-secure when you want to protect sensitive content (performance reviews, credentials configs, private docs, client data) in a git repo while keeping it version-controlled and seamlessly readable locally.
Prerequisites
git-crypt(installed viabrew install git-crypt)age(installed viabrew install age)- An initialized git repo
Verification Gate (never tell the user they're protected without proving it)
You MUST NOT tell the user their data is encrypted, "safe to push," or "protected" until you have run the verification below and shown them its output. Writing .gitattributes is not proof — a malformed or mis-scoped attributes file silently no-ops encryption, leaving plaintext committed while the user believes they are covered. Claiming protection on the basis of setup steps alone is a confirm-without-verify failure. Prove it, then claim it.
Before saying anything reassuring, run and show BOTH checks:
-
git-crypt status — confirm the intended files are listed as
encrypted, notnot encrypted:git-crypt status <target>/Every file you meant to protect must appear under
encrypted. If any appears asnot encrypted, encryption is NOT working — stop and fix.gitattributes(wrong path, wrong glob, or.gitattributesnot committed) before continuing. -
Prove the committed blob is ciphertext — inspect what git actually stored, not the working tree (the working tree is always plaintext and tells you nothing):
git show HEAD:<target>/<file> | head -c 200 | xxd | headYou must see the git-crypt header (the file begins with the bytes
\0GITCRYPT\0) followed by binary ciphertext — NOT readable plaintext. If you can read the secret in this output, it is committed in the clear. Do not tell the user they are protected.
Only after BOTH checks pass — files listed as encrypted AND committed blob shows ciphertext — may you state that encryption is working. Quote the actual command output to the user; do not paraphrase "looks good."
History-leak gate (encryption does not retroactively protect old commits)
If any file you are about to encrypt was already committed in plaintext (check with git log --all --full-history -- <path> — any prior commits mean it has been stored in the clear), you MUST surface this prominently BEFORE any reassurance:
⚠️ History leak:
<path>already exists as plaintext in earlier commits (and on any remote you've already pushed to). Turning on encryption now only protects future commits — it does NOT scrub the plaintext sitting in past commits. Anyone with repo or remote access cangit show <old-sha>:<path>and read it. Pushing now does not fix this.
Then offer the remediation path explicitly — do not let the user believe new encryption covers old history:
- Scrub history with
git filter-repo(rewrites every commit to remove/re-encrypt the file; destructive, rewrites SHAs, requires force-push and coordination with anyone who has clones). Confirm explicitly before running. - Rotate the exposed secrets — if the leaked plaintext contained credentials/keys/tokens, treat them as compromised and rotate them regardless of scrubbing, since the plaintext may already have been cloned or cached.
Never answer "is my data safe to push now?" with an unqualified "yes" when prior plaintext commits exist. The correct answer is: future commits are encrypted (proven above), but the existing history still leaks until scrubbed and the exposed secrets are rotated.
Interactive Flow
When invoked, walk through these steps with the user:
Step 1: What to Encrypt
Ask: "What do you want to encrypt?" Accept:
- A folder path (e.g.,
touchpoint/,secrets/config/) - A glob pattern (e.g.,
*.secret.yml,private/**) - Multiple targets
Step 2: Snapshot Strategy
Ask: "Do you want versioned snapshots for recovery?" Options:
- Yes (recommended): Create age-encrypted tar snapshots before modifications
- No: Rely on git history alone
If yes, ask:
- Snapshot location (default:
<target>/.snapshots/) - Auto-snapshot before modify? (default: yes)
Step 3: Key Management
Ask: "Where should backup keys be stored?"
- git-crypt key default:
~/.git-crypt-backups/<repo-name>.key - age key default:
~/.age/<repo-name>-snapshots.key - Remind user to back up to a secure external location
Step 4: Execute Setup
- Check dependencies: Verify git-crypt and age are installed. Offer to
brew installif missing. - Initialize git-crypt if not already done (
git-crypt init) - Write
.gitattributesin the target directory:* filter=git-crypt diff=git-crypt .gitattributes !filter !diff - Generate age key for snapshots (if enabled)
- Export and backup git-crypt key
- Handle already-committed files: For every target, run
git log --all --full-history -- <path>. If it returns prior commits, the file is already plaintext in history — trigger the History-leak gate (see Verification Gate section above): warn prominently, re-stage (git rm --cached+git add) so the next commit stores ciphertext, offergit filter-reposcrubbing (destructive — confirm explicitly), and recommend rotating any exposed secrets. - Create initial snapshot (if snapshots enabled)
- Create git tag at the encryption baseline:
git-secure/<target>-baseline - RUN THE VERIFICATION GATE: Commit the
.gitattributes(and re-staged files), then run both checks from the Verification Gate section —git-crypt status <target>/andgit show HEAD:<target>/<file> | xxd | head. Show the output. Do NOT proceed to tell the user they are protected until both pass. - Update CLAUDE.md with encryption instructions specific to this repo
Step 5: Update CLAUDE.md
Append an "Encrypted Directories" section (or update existing) with:
- Which paths are encrypted
- "Always read from working tree, not git objects"
- "If files appear as binary, run
git-crypt unlock" - Snapshot creation command with the correct age public key
- Key backup locations
- "Never force-push branches with encrypted content"
- Auto-snapshot rule: "Before modifying encrypted files, create a snapshot first"
CLAUDE.md Template Block
## Encrypted Directories
`<target>/` is encrypted via **git-crypt**. Contains <description>.
- **Locally:** Files are plaintext when repo is unlocked. Read/edit normally from the working tree.
- **On GitHub:** Files appear as encrypted binary blobs.
- **Fresh clone:** Run `git-crypt unlock` once after cloning.
- **Never read encrypted files from git objects** (e.g., `git show HEAD:path/...`) — use the working tree.
- **Before modifying encrypted files:** Create a snapshot:
tar cf - --exclude='<target>/.snapshots' <target>/ | age -r <PUBLIC_KEY> -o <target>/.snapshots/YYYY-MM-DD-description.tar.age
- **Key backups:**
- git-crypt: `~/.git-crypt-backups/<repo>.key`
- age: `~/.age/<repo>-snapshots.key`
- **Never force-push branches with encrypted content.**
Recovery Procedures
Decrypt a snapshot
age -d -i ~/.age/<repo>-snapshots.key <target>/.snapshots/<snapshot>.tar.age | tar xf -
Unlock repo on fresh clone
git-crypt unlock # uses key from ~/.git-crypt/keys/default
# OR
git-crypt unlock ~/.git-crypt-backups/<repo>.key
Verify encryption status
Run the full Verification Gate (both checks), not just git-crypt status alone:
git-crypt status <target>/ # files must show as "encrypted"
git show HEAD:<target>/<file> | head -c 200 | xxd # committed blob must be ciphertext, not plaintext
Status alone can be misleading on an uncommitted or partially-staged tree — always confirm the committed blob is ciphertext before declaring success.
Edge Cases
- Monorepo with multiple encrypted dirs: Each gets its own
.gitattributes. All share the same git-crypt key. - Adding encryption to an existing repo with CI: CI won't be able to read encrypted files without the key. Document this.
- Nested
.gitattributes: git-crypt respects the closest.gitattributes, so encryption scopes naturally to subdirectories. - Large binary files in encrypted dirs: git-crypt encrypts deterministically, so identical plaintext = identical ciphertext. But encrypted diffs are useless — rely on snapshots for large binary rollback.
What This Skill Does NOT Do
- Manage secrets in environment variables or CI (use a secrets manager)
- Encrypt individual values inside files (use
sopsfor that) - Provide access control (anyone with the git-crypt key can decrypt everything)
- Replace proper credential management for API keys/tokens