Encrypted, namespaced secret store for AI coding agents
Project description
cubby
Encrypted, namespaced secret store for AI coding agents — secrets reach the command, never the agent's context.
AI coding agents are great at running commands — but everything they read from a command's output lands in their context and transcripts. Paste a database password into a prompt once and it's logged forever.
cubby fixes that. Secrets live encrypted on disk (via age).
The agent never reads a value — it runs cubby run -- <command>, and cubby injects
the secrets straight into the command's environment. The agent sees the command's
result, never the secret itself.
How it works
flowchart LR
agent["AI agent"]
cubby["cubby run"]
store[("encrypted<br/>namespace")]
child["child process<br/>(psql, curl, …)"]
agent -- "cubby run -- <cmd>" --> cubby
store -- "decrypt in memory" --> cubby
cubby -- "secrets as env vars" --> child
child -- "output only" --> agent
The decrypted value exists only in the environment of the child process. It is never printed, never written to disk in cleartext, and never enters the agent's context.
Install
brew install age # required: age + age-keygen (Linux: github.com/FiloSottile/age/releases)
git clone https://github.com/perlyer/cubby.git && cd cubby && ./install.sh
install.sh puts cubby on your PATH, runs cubby init, and offers to install the
integration into any AI coding agents it finds. Non-interactive:
./install.sh --key-mode keychain --agent claude-code,codex
--key-mode keychain (macOS) stores the age key in the login Keychain so it unlocks
automatically at login; the default --key-mode file keeps it in
~/.config/cubby/identity (mode 0600).
Install from PyPI
pipx install cubby-secrets
This puts the cubby command on your PATH (the distribution is named
cubby-secrets because cubby was already taken on PyPI; the command itself is
cubby). It does not set up the AI-agent integration — run cubby agent add <agent> for that, or use the git clone + ./install.sh flow above, which does
it for you.
Updating
./update.sh
update.sh checks out the latest release tag in your clone, re-links cubby on your
PATH, and refreshes the agent integrations. It aborts if the working tree has
uncommitted changes. Your secret store in ~/.config/cubby/ is never touched.
Usage
| Command | What it does |
|---|---|
cubby init |
First-run setup — generates the age key, creates the config |
cubby set <name> |
Store a secret (hidden prompt, or --stdin); --env VAR sets its env var |
cubby get <name> |
Show metadata — --reveal prints plaintext, --copy copies it (humans only) |
cubby list |
List secret names in the namespace |
cubby rm <name> |
Delete a secret |
cubby rename <old> <new> |
Rename a secret |
cubby cp <name> <ns> |
Copy a secret to another namespace |
cubby mv <name> <ns> |
Move a secret to another namespace |
cubby rotate <name> |
Replace a secret's value (tracks a rotation count) |
cubby ttl [<name> [<dur>]] |
Show or change a secret's expiry |
cubby run -- <cmd> |
Run a command with the namespace's secrets in its environment (--only/--except to scope) |
cubby import <type> <src> |
Bulk import — dotenv, aws, json, 1password, ns |
cubby map |
Show or change the environment variable each secret is injected as |
cubby ns add|list|rm|use|rename |
Manage namespaces |
cubby agent add|list|rm|refresh |
Manage AI-agent integrations |
cubby doctor |
Check the install, key, config and namespaces for problems |
cubby completion <shell> |
Print a shell completion script (bash/zsh/fish) |
cubby audit |
Show or manage the opt-in audit log |
cubby export <file> |
Write a passphrase-encrypted backup of the whole store |
cubby restore <file> |
Restore the store from a backup bundle |
A typical session:
$ cubby set db-password
value for 'db-password':
cubby: secret 'db-password' set in namespace 'work'
$ cubby get db-password
name: db-password
namespace: work
env var: DB_PASSWORD (default)
length: 18
updated: 2026-05-17T09:14:02+00:00
expires: never
rotated: never
$ cubby run -- psql -h 127.0.0.1 -U appuser -d appdb
psql (16.2)
appdb=>
cubby get never prints the value without --reveal; cubby set reads it from a
hidden prompt, so it never lands in shell history or argv.
Namespaces
A namespace is a workspace or environment (work, personal, …). The active namespace
is resolved per command: -n <name> flag → $CUBBY_NS → working-directory prefix
match → default.
cubby ns add work --cwd-prefix ~/projects/work
cubby ns # show the active namespace and why it was chosen
Each namespace is a separate encrypted file. Under cubby run every secret is
injected as an environment variable — by default the UPPER_SNAKE of its name
(db-password → DB_PASSWORD). To inject it under a different name, use
cubby set <name> --env VAR or cubby map <name> VAR (e.g. db-password →
PGPASSWORD); cubby map with no arguments lists the current mapping.
A secret can carry an optional expiry. cubby set <name> --ttl 30d stores it
with a 30-day TTL; cubby ttl <name> 90d changes the expiry later without
re-entering the value, and cubby ttl <name> none clears it. An expired secret
is never deleted — cubby run still injects it, with a warning, and
cubby doctor flags it. cubby rotate <name> replaces a secret's value and, if
it had a TTL, gives it a fresh one.
Agent integration
cubby agent installs a small integration — a skill or instructions file, plus a
permissions allowlist where the agent supports one — so the agent reaches for
cubby run instead of reading secrets in plaintext.
cubby agent list # adapters and their status
cubby agent add claude-code # install the integration for one agent
cubby agent rm claude-code # remove it
Supported agents: claude-code, codex, gemini, cursor, copilot.
Claude Code users can alternatively use the native plugin marketplace:
/plugin marketplace add perlyer/cubby
/plugin install cubby
The integration is a convention — it asks the agent to use cubby run. For an
enforced guardrail, also block cubby get --reveal and cubby get --copy in your
agent's permission system; see Hardening for AI agents.
Security
cubby keeps secrets encrypted at rest and out of an agent's context — but it is a
guardrail, not a sandbox. It does not protect against a compromised machine or an
agent that runs arbitrary code. Read SECURITY.md for the full threat
model and how to report a vulnerability.
Audit log
cubby can keep a local log of every time a secret value leaves the store — a
cubby run, a cubby get --reveal, or a cubby get --copy. It is off by
default; turn it on with cubby audit --enable. The log records a timestamp,
the event, the namespace, and (for run) the command — never a secret value.
cubby audit shows it, cubby audit --clear erases it. It lives at
~/.config/cubby/audit.log.
The log self-rotates at ~1 MB: when it reaches that size the current file is
moved to audit.log.1 and a fresh one is started, so it never grows without
bound. cubby audit --all shows both the current log and the rotated history.
Backup
Losing the age identity makes every namespace unrecoverable, so back the store
up. cubby export backup.age writes a single passphrase-encrypted bundle
containing the identity, config, and every namespace; age prompts for the
passphrase. Because the bundle is passphrase-encrypted it is safe to store
off-machine.
cubby restore backup.age rebuilds the store from a bundle on a fresh machine.
It refuses to overwrite an existing store unless you pass --force. The restored
store always uses file key-mode — if the bundle was created in keychain mode,
cubby restore reports this so you can re-migrate to the Keychain afterwards.
Shell completion
cubby completion bash (or zsh / fish) prints a completion script for
cubby's commands. For bash, add eval "$(cubby completion bash)" to ~/.bashrc;
for zsh, add eval "$(cubby completion zsh)" to ~/.zshrc;
for fish, cubby completion fish > ~/.config/fish/completions/cubby.fish.
Contributing
Contributions welcome — including new agent adapters (one file each). See CONTRIBUTING.md.
License
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file cubby_secrets-0.7.0.tar.gz.
File metadata
- Download URL: cubby_secrets-0.7.0.tar.gz
- Upload date:
- Size: 50.2 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.12.10
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
2233fe2d97ab2381c18bc9db23b5300eeb138791194a8f124cc7938cdbf92010
|
|
| MD5 |
879e0c85f49e97410d978079558036d8
|
|
| BLAKE2b-256 |
744303e54d65b3736b117874f5b844a2a6e28454273ca5c8951f71c7a99310be
|
File details
Details for the file cubby_secrets-0.7.0-py3-none-any.whl.
File metadata
- Download URL: cubby_secrets-0.7.0-py3-none-any.whl
- Upload date:
- Size: 32.3 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.12.10
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
f42d988cc6f404ba51f46a71e58db3784f43113693cd11414b20c70960e80356
|
|
| MD5 |
7ecdacee9aeab245fc1a01a90335b30f
|
|
| BLAKE2b-256 |
568fa7a4f9350afc9a7e6fd5636cdbaea7ad02a2bc8c16c9af6ce760a4866fca
|