proof of concept

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.

From any git repo
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.

1
Install
pip install yoink-py
brew install age

Requires Python 3.11+ and age. macOS and Linux.

2
Bootstrap and edit 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.

3
Run with secrets
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.

This is a proof of concept

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

the problem

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.

the bet

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

01
First use bootstraps everything
Run any command inside a git repo. yoink creates .yoink/ with encrypted environment files, generates your identity keypair in ~/.yoink/, and prints two vault-wide recovery keys — once.
02
Edit secrets like a file
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.
03
Access via PR
New developer runs 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.
04
Inject into any process
yoink run dev -- python app.py decrypts the environment and injects secrets as env vars. No config changes to your app.
secrets buffer
# 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
access buffer
# 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.