Encrypted token store for developers. Single file, zero dependencies, git-synced.
Manage your API tokens and secrets from the terminal. AES-256 encrypted, decrypted with a local master key. Data at ~/.tokenvault/, key at ~/.config/tokenvault/ — without the key, it's unreadable.
Single JS file. No dependencies. Just Node.js.
npm i -g tokenvaultOr clone and link:
git clone https://github.com/saadnvd1/tokenvault && cd tokenvault && npm linkThen generate your master key:
tv inittv add stripe sk_live_example123 "secret key (prod)"
tv add stripe whsec_xyz789 "webhook secret"
tv get stripe # prints all tokens for project
tv get stripe "secret key (prod)" # prints specific token
tv list # all projects
tv list stripe # tokens for project (masked)
tv remove stripe "webhook secret" # remove one token
tv remove stripe # remove all for project
tv dump # full decrypted JSON
tv audit # who read what, and when (never values)tv initcreates a git repo at~/.tokenvault/and generates a master key at~/.config/tokenvault/master.keytv addencrypts all tokens with AES-256-CBC (Node crypto) and writestokens.enc- Every write auto-commits
tokens.encto the local git repo master.keystays outside the repo, never committed
That's it. Single JS file, zero deps.
# First machine — set up remote:
tv remote https://github.com/you/my-tokens.git # private repo
tv push
# Second machine:
npm i -g tokenvault
tv init
tv remote https://github.com/you/my-tokens.git
tv pull
# Copy the master key from your first machine:
scp user@first-machine:~/.config/tokenvault/master.key ~/.config/tokenvault/master.key
chmod 600 ~/.config/tokenvault/master.keyNow tv get works on both machines. Use tv push / tv pull to sync.
Tell any AI coding agent (Claude Code, Codex, etc.) to use tv and it can fetch tokens on its own:
# In your project's CLAUDE.md or agent instructions:
# "Use `tv get <project> [desc]` to fetch API keys. Run `tv list` to see what's available."Output is pipe-safe — colors auto-disable when redirected, so tv get openai "api key" returns a clean value for scripts and subshells.
# In a script or .env setup:
export OPENAI_API_KEY=$(tv get openai "api key")
export STRIPE_SK=$(tv get stripe "secret key (prod)")Every tv get and tv dump appends one JSON line to
~/.local/state/tokenvault/audit.log (override with TOKENVAULT_AUDIT_LOG;
XDG_STATE_HOME is respected). It records which entry was read, never its
value:
{"ts":"2026-09-16T22:50:00.000Z","cmd":"get","entries":["stripe/secret key"],"found":true,
"pid":4242,"ppid":4241,"parents":["zsh","claude","tmux"],"cwd":"/Users/me/app",
"claude_session":"6f1c…","agent":"task-nova-bear"}parentsis the executable names of the parent chain, not command lines — a parent's argv can carry some other secret.claude_sessioncomes fromCLAUDE_CODE_SESSION_ID(orCLAUDE_SESSION_ID),agentfromWIRE_AGENT; both arenullwhen unset.- The log is outside
~/.tokenvault/on purpose: that directory is a git repo that pushes to a remote. The file is created0600. - Logging is best-effort. If the log cannot be written,
tv getstill prints the token and exits 0.
tv audit # last 20 reads
tv audit -n 100
tv audit check # print anomalies; exit 3 if there are any
tv audit check --post # post new ones to wire, remember what was senttv audit check flags:
| Finding | Rule (defaults) | Flags |
|---|---|---|
dump |
any tv dump |
|
burst |
more than 5 distinct entries read inside 60s | --max, --window |
misses |
3+ reads of entries that do not exist inside 60s (probing) | --max-misses |
With --post it sends the findings to the wire channel tokenvault-audit
(--channel or TOKENVAULT_AUDIT_CHANNEL; creates it if missing), as
WIRE_AGENT=tokenvault-audit, and stores a cursor in audit.log.state.json
so a finding is posted once. If the post fails the cursor is not moved and the
next run retries. TOKENVAULT_WIRE overrides the wire binary (tests use it).
Run it every five minutes with serviceman:
sm add tv-audit-check "/opt/homebrew/bin/node $HOME/.local/share/homelab-jobs/tokenvault/cli.js audit check --post" --cron "*/5 * * * *"On Saad's Mac, cron jobs must run pushed code from a job checkout, never
~/dev, so this belongs in homelab/box/services.toml as a mac_jobs entry
rather than a hand-added sm job:
{ name = "tv-audit-check", cmd = "/bin/zsh -c 'source ~/.zshenv; exec /opt/homebrew/bin/node \"$HOME/.local/share/homelab-jobs/tokenvault/cli.js\" audit check --post'", cron = "*/5 * * * *", checkouts = ["tokenvault"] },then box/homelab apply --mac and box/homelab check. ~/.zshenv supplies
WIRE_REMOTE/WIRE_TOKEN_FILE, so the post reaches the box's wire store.
npm testZero deps: node --test. The CLI tests run cli.js against a throwaway
HOME, vault and log.
Tokens are encrypted with AES-256-CBC + PBKDF2 via Node's built-in crypto module. The encrypted file is safe to push to GitHub. Security depends on your master key staying private — treat it like an SSH key.
Password managers are designed for web logins. tokenvault is designed for API tokens — things you export in shell scripts, paste into .env files, and reference in CI/CD configs. Different workflow.
Env vars work for one machine. tokenvault works across machines via git sync. It's also searchable — tv list shows everything, tv get project finds what you need instantly.
Yes. Requires Node.js 16+ (uses built-in crypto, no native deps).
- Keyring Vault — macOS menu bar app for tokenvault. Touch ID, search, one-click copy.
MIT
