پنهان · Persian for hidden
Secrets in Git.
Encrypted.
Penhan is a small CLI for teams that want Git to be the source of truth for their secrets. They live in the repository as encrypted files, reviewed and versioned like code. Penhan decrypts them on your machine and pushes what changed to HashiCorp Vault or Kubernetes.
xxd. The key stays in .penhan/keys/, which is gitignored.§ 1 · How it works
Encrypt. Commit. Push what changed.
-
01
Encrypt
Write a flat YAML or JSON map, run
penhan encrypt. Plaintext and keys are gitignored for you; only.encfiles get committed.$ penhan encrypt Encrypted: secrets/db.yaml -
02
Commit
Review secrets in pull requests like any other change. Re-encrypting an unchanged secret leaves its file alone, so diffs stay clean.
$ git status --short -uall ?? penhan.yaml ?? secrets/db.yaml.enc
-
03
Push
checkcompares each secret with the backend;pushwrites only the new and changed ones.$ penhan push Pushed (new): db Push complete: 1 pushed, 0 unchanged
Real output from penhan v0.6 against a throwaway Vault dev server.
§ 2 · Backends
One repository, many safes.
A project holds one or more safes. Each is a directory with its own penhan.yaml, key and backend, so one repository can manage secrets for several apps, environments or namespaces.
- HashiCorp Vault KV v2, with a token file per safe.
- Kubernetes Secrets in a namespace, pinned to one kubeconfig context.
- An encrypted directory, for anything else.
§ 3 · Guardrails
Safe by default, because it's your secrets.
- Pins the context
- A Kubernetes safe is tied to one kubeconfig context, so a push can't land in the wrong cluster.
- Never takes over
- It won't overwrite a Secret it didn't create.
penhan importadopts existing ones without changing them. - Never regenerates a key
- A missing key is an error, not a reason to make a new one and orphan every file.
- Byte for byte
- Secret values reach the backend exactly as written: no trimming, no re-encoding.
- Changes only
- Hashes decide what's new or changed; unchanged secrets are skipped and reported as such.
- Nothing to learn from history
- AES-256-GCM or OpenPGP, keyed per safe and randomized on every encryption, so Git history never shows when two secrets match or a value changes back.
§ 4 · Configuration
One small file per safe.
penhan add writes it for you, with flags or interactively. This is the one from the session above.
# myapp/penhan.yaml encryption: method: aes aes: key_path: .penhan/keys/aes.key backend: type: vault vault: addr: http://127.0.0.1:8200 token_path: .penhan/vault-token mount_path: secret base_path: myapp secrets: path: secrets/ format: yaml
§ 5 · Install
Prebuilt for macOS and Linux.
OS=$(uname -s | tr '[:upper:]' '[:lower:]')
ARCH=$(uname -m | sed 's/x86_64/amd64/; s/aarch64/arm64/')
VERSION=$(curl -fsSLI -o /dev/null -w '%{url_effective}' https://github.com/miladbeigi/penhan/releases/latest | sed 's|.*/v||')
curl -fsSL "https://github.com/miladbeigi/penhan/releases/download/v${VERSION}/penhan_${VERSION}_${OS}_${ARCH}.tar.gz" | tar -xz penhan
sudo install penhan /usr/local/bin/
go install github.com/miladbeigi/penhan/cmd/penhan@latest
Requires Go 1.26 or later.
Update any time with penhan update, which installs the latest release after checking its checksum. Then run penhan add for an interactive setup, or follow Getting started.