Secrets that live
in your repo.
Age-encrypted secrets committed alongside your code. Each developer holds their own key. Access is managed via git pull requests, not dashboards. No external services, no secret sprawl.
cd your-repo yoink secrets yoink run dev -- pnpm start
Quick start
Three steps. Run them from any git repo where you want encrypted secrets.
cd your-repo yoink secrets
First run creates .yoink/ with dev, staging, and production environments, then opens your editor. Add key=value lines, save and quit. Two recovery keys print once — back them up.
yoink run dev -- python app.py
Decrypts the dev environment and injects secrets as env vars. Works with any command — no app changes needed.
Commands
yoink secrets
Edit all secrets in $EDITOR
yoink run <env> -- <cmd>
Run a command with secrets injected
yoink access edit
Review members and access requests in $EDITOR
yoink access request
Request vault access (new developers)
Walk through a full example in the example/ directory, or read the README.
The code was written fast to test an idea. It works, with real limitations. If it gets traction, the goal is a Go rewrite using the Charm Bracelet stack — a single binary, no Python required, with the same vault format so nothing needs migrating. Read the roadmap.
The idea
Every secrets management tool either costs money, requires an external service, or involves so much setup that small teams just use a shared .env in Slack.
Age-encrypted files in the repo give you version control and PR-based access management for free. And age encryption is probably secure enough for most teams — few moving parts, tiny attack surface, no external service to compromise. You lose auditing and centralised control. You gain simplicity.
How it works
yoink creates
.yoink/ with encrypted environment files,
generates your identity keypair in ~/.yoink/,
and prints two vault-wide recovery keys — once.
yoink secrets opens
a plaintext buffer in $EDITOR. All
environments, all secrets. Edit values, add keys, delete
lines, add new [environment] sections. Save
and quit — changes are diffed and re-encrypted.
yoink access request, commits the resulting JSON file, and opens a PR. A
maintainer runs
yoink access edit,
moves the request line into the members list, saves. Vault
files are re-encrypted.
yoink run dev -- python app.py
decrypts the environment and injects secrets as env vars. No
config changes to your app.
# yoink secrets — edit freely, save to apply [dev] DATABASE_URL=postgres://localhost/mydb API_KEY=sk_test_abc [staging] DATABASE_URL=postgres://staging/mydb [production] DATABASE_URL=postgres://prod/mydb
# move a request into members to approve, delete to reject ## members jack dev staging production sarah dev staging ## requests bob dev staging
Limitations
These are real. Know them before deciding whether this is right for your use case.
- Git history is immutable. Revoking access doesn't erase past exposure. Rotate critical secrets after revoking someone.
- No audit log. Who decrypted what and when is not tracked.
- Re-encryption is not atomic. A crash mid-operation could leave files inconsistent.
- Proof of concept. Error handling is basic. Edge cases exist. Not hardened for adversarial environments.
See ROADMAP.md for what a more complete version would address.
Try it. Break it. Tell me.
If it works for you, star the repo. If it breaks, open an issue. Both are useful.