# Git Crypt

> Manage git-crypt encrypted repos end-to-end: UNLOCK (auto-fetch GPG key + passphrase from Bitwarden via rbw, non-interactive), LOCK (re-scramble the local working tree before leaving a shared machine), SETUP (encrypt internal docs in a new public repo: init, authorize key, .gitattributes patterns), and STATUS. Use when encrypted GITCRYPT blobs appear after a clone, when leaving a machine, when on…

- **Type:** Skill
- **Install:** `agentstack add skill-fei2-labs-skill-genie-git-crypt`
- **Verified:** Yes — security-reviewed for prompt injection and unsafe behavior
- **Seller:** [Fei2-Labs](https://agentstack.voostack.com/s/fei2-labs)
- **Installs:** 0
- **Category:** [Agent Skills](https://agentstack.voostack.com/c/agent-skills)
- **Latest version:** 0.1.0
- **License:** MIT
- **Upstream author:** [Fei2-Labs](https://github.com/Fei2-Labs)
- **Source:** https://github.com/Fei2-Labs/skill-genie/tree/main/skills/git-crypt

## Install

```sh
agentstack add skill-fei2-labs-skill-genie-git-crypt
```

Requires the [AgentStack CLI](https://agentstack.voostack.com/docs/cli). Works with Claude Code, Cursor, and any MCP-compatible agent.

## About

# git-crypt Manager

Four operations on git-crypt repos. Pick by user intent:

| User says | Operation |
|---|---|
| "解锁仓库 / unlock / 文件是乱码" | **UNLOCK** |
| "锁定仓库 / lock / 我要离开这台机器" | **LOCK** |
| "给这个仓库加密 / encrypt this repo / 上 git-crypt" | **SETUP** |
| "加密状态 / 哪些文件加密了" | **STATUS** |

Mental model (explain to user if confused): day-to-day **encryption is automatic** — files matched by `.gitattributes` are encrypted by the filter on `git add`/commit; pushed content is always ciphertext. `lock` only re-scrambles the LOCAL working tree; `unlock` reverses that.

## Personal configuration (this user)

| Setting | Value |
|---|---|
| GPG key id | `25FFD2B10C7545B6` (uid `clarezoe`, sign ed25519 + encrypt cv25519 subkey) |
| Bitwarden item (rbw) | discover by keyword: `rbw list \| grep -i 'git-crypt'` (exactly one match expected). Layout: item **password** = key passphrase; custom field **`key`** = armored private key (stored single-line: Bitwarden web-UI fields strip newlines — MUST reconstruct, see U4); field **`rev`** = revocation cert |
| gpg-preset-passphrase | `/opt/homebrew/opt/gnupg/libexec/gpg-preset-passphrase` |

Never echo the passphrase or private key into output/logs; pipe directly between commands.

---

## STATUS

```bash
cd "$(git rev-parse --show-toplevel)"
ls .git-crypt 2>/dev/null || { echo "repo not git-crypt managed"; exit 0; }
git-crypt status -e        # encrypted files
# Locked or unlocked? Inspect one encrypted file's working copy:
f=$(git-crypt status -e 2>/dev/null | awk '{print $2}' | head -1)
[ -n "$f" ] && { head -c 9 "$f" | grep -q GITCRYPT && echo "working tree: LOCKED" || echo "working tree: UNLOCKED (plaintext locally)"; }
```

---

## UNLOCK

Run steps in order; skip any step whose check already passes (idempotent).

### U1. Preconditions
```bash
which git-crypt gpg rbw || brew install git-crypt gnupg rbw
git rev-parse --show-toplevel    # must be a git repo with .git-crypt/
```

### U2. Already unlocked? → use STATUS; if "UNLOCKED" → done.

### U3. GPG key present?
```bash
gpg --list-secret-keys 25FFD2B10C7545B6 >/dev/null 2>&1 || echo "missing → U4"
```

### U4. Import key from Bitwarden (only if missing)
`rbw unlock` pops a pinentry for the **Bitwarden master password — the user types it** (the single interactive moment).
```bash
rbw unlock
# Discover the item by keyword — never hardcode the name:
N=$(rbw list | grep -i 'git-crypt' | head -1)
[ -z "$N" ] && { echo "no vault item matching 'git-crypt' — ask user"; }
rbw get "$N" --field key | head -c 30 | grep -q 'BEGIN PGP PRIVATE KEY' || echo "item '$N' has no 'key' field with PGP armor — wrong item, ask user"
# Field 'key' is single-line (web UI strips newlines) — reconstruct armor, pipe to import:
rbw get "$N" --field key | python3 -c "
import sys,re
s=sys.stdin.read().strip()
m=re.match(r'-----BEGIN PGP PRIVATE KEY BLOCK-----\s*(.*?)\s*-----END PGP PRIVATE KEY BLOCK-----', s, re.S)
print('-----BEGIN PGP PRIVATE KEY BLOCK-----'); print()
for t in m.group(1).split(): print(t)
print('-----END PGP PRIVATE KEY BLOCK-----')
" | gpg --batch --import
gpg --list-secret-keys 25FFD2B10C7545B6   # verify imported
```
If discovery finds 0 or 2+ matches: show the candidates to the user and ask which to use; do not guess. If `rbw sync` fails with `missing field access_token` (vaultwarden token incompat): `rbw purge` then `rbw login` (fresh token chain fixes it). rbw pinentry must be GUI: `rbw config set pinentry /opt/homebrew/bin/pinentry-mac`.

### U5. Preset passphrase into gpg-agent (non-interactive unlock)
```bash
grep -q allow-preset-passphrase ~/.gnupg/gpg-agent.conf 2>/dev/null || { echo allow-preset-passphrase >> ~/.gnupg/gpg-agent.conf; gpgconf --kill gpg-agent; }
for grip in $(gpg --list-secret-keys --with-keygrip 25FFD2B10C7545B6 | awk '/Keygrip/{print $3}'); do
  rbw get "$N" | /opt/homebrew/opt/gnupg/libexec/gpg-preset-passphrase --preset "$grip"   # item password = passphrase ($N from U4 discovery)
done
```

### U6. Unlock + verify
```bash
cd "$(git rev-parse --show-toplevel)" && git-crypt unlock
head -c 9 "$f" | grep -q GITCRYPT && echo "STILL LOCKED — investigate" || echo "unlocked ✓"
```

---

## LOCK

Re-scrambles the local working tree (remote is always ciphertext regardless).

```bash
cd "$(git rev-parse --show-toplevel)"
git status --short            # encrypted files must be committed first — lock refuses dirty encrypted files
git-crypt lock
# Verify:
f=$(git-crypt status -e 2>/dev/null | awk '{print $2}' | head -1)
head -c 9 "$f" | grep -q GITCRYPT && echo "locked ✓" || echo "NOT locked — investigate"
```
If `git-crypt lock` complains about staged/modified files: commit or stash the encrypted-file changes first, then retry. After lock, editing those files is meaningless until the next unlock — warn the user.

---

## SETUP (encrypt internal docs in a repo)

For PUBLIC repos holding internal docs. (PRIVATE repos don't need this — commit docs plainly.)
Confirm with `gh repo view --json visibility` first; if PRIVATE, tell the user it's unnecessary and stop unless they insist.

```bash
cd "$(git rev-parse --show-toplevel)"
git-crypt init
git-crypt add-gpg-user --trusted 25FFD2B10C7545B6     # makes its own commit
cat >> .gitattributes ` then `rbw login`.
- New machine checklist: brew install git-crypt gnupg rbw → rbw login → say "解锁仓库" in the target repo.

## Source & license

This open-source skill is cataloged on AgentStack and links to its original source — we do not rehost the code.

- **Author:** [Fei2-Labs](https://github.com/Fei2-Labs)
- **Source:** [Fei2-Labs/skill-genie](https://github.com/Fei2-Labs/skill-genie)
- **License:** MIT

Install and usage instructions live in the source repository linked above.

## Pricing

- **Free** — Free

## Security capabilities

Automated source analysis of v0.1.0 — what this tool can access:

- **Network access:** no
- **Filesystem access:** no
- **Shell / process execution:** no
- **Environment & secrets:** no
- **Dynamic code execution:** no

*"Yes" means the capability is present in the source — more access means more to trust, not that it is unsafe.*


## Versions

- **0.1.0** — security scan: passed — Imported from the upstream source.

## Links

- Listing page: https://agentstack.voostack.com/l/skill-fei2-labs-skill-genie-git-crypt
- Seller: https://agentstack.voostack.com/s/fei2-labs
- Browse the marketplace: https://agentstack.voostack.com/browse

---
Listed on AgentStack — the marketplace for AI agent skills and MCP servers. Every listing is security-reviewed. Creators keep 70%.
