Skip to main content

tokenstash

Paste a key once. Approve each directory. Keep secrets out of status output.

A local credential broker for coding agents. When your agent needs an API key, it asks tokenstash instead of asking you to paste it into the chat. The key goes into your project's env file, and the agent gets back a status line instead of the key.

Download · Security · Changelog

Tests MIT macOS and Linux MCP No telemetry

Without tokenstash, the agent stops and asks you to paste keys into the chat, and the key you paste becomes part of the conversation. With tokenstash, the agent runs one command: keys you have are injected into .env.local, and a key you don't have gets a link.

Install

brew install kgarg2468/tokenstash/tokenstash
# or
npm install -g tokenstash
# or
uv tool install tokenstash

Then, once:

tokenstash init

init picks your OS keychain and adds tokenstash to the agents it finds: Claude Code, Codex, Cursor and Gemini CLI. macOS and Linux; Windows is not supported yet.

Other ways to install
bun add -g tokenstash
pnpm add -g tokenstash
pipx install tokenstash
cargo install --locked --git https://github.com/kgarg2468/tokenstash tokenstash

Prebuilt binaries for macOS (Apple Silicon, Intel) and Linux (x64, arm64; static, any distribution) are on the latest release, with sha256 files and a build attestation you can check with gh attestation verify tokenstash-<platform>.tar.gz --repo kgarg2468/tokenstash. The npm and PyPI packages carry the same binary and run no install scripts.

How it works

The agent runs tokenstash need. If the key is stored and this folder is approved, it is written to .env.local and the agent gets a status line, not the value. If the key is stored but the folder isn't approved yet, you approve it once in the inbox on localhost. If the key isn't stored yet, you are notified with a link to a page on localhost, you paste it once, and it is stored and written.

When the agent needs a key, it runs tokenstash need OPENAI_API_KEY (or asks over MCP). There are three outcomes:

  • You have the key and this folder is approved: it's written to .env.local and the agent carries on.
  • You have the key but this folder hasn't been approved: you get a notification. Open it, see exactly which keys would go into which file, and approve once.
  • You don't have the key yet: you get a notification and a link to a page tokenstash serves on localhost, with the provider's signup link and the steps. Paste the key there once; it's stored in your OS keychain, and every later project can get it from there.

One key, several folders. The folder where you pasted it has it. The first time another folder asks, you approve it once in the inbox on localhost, and that folder is quiet for that key after. A folder that hasn't asked yet gets one approval when it does. Sensitive keys ask for each folder separately.

  • Approval is per folder, and you choose how wide. The first time a folder asks for keys you already have, you get three choices: Allow these (just the listed keys), Allow these + any non-sensitive key here (also future registry-known, non-sensitive keys), or Deny (remembered for 24 hours by default).
  • Sensitive keys always ask. Live Stripe secret keys, AWS credentials, deploy and package-registry tokens, and any key tokenstash doesn't recognise get their own approval in each folder. The broad choice never covers them.
  • Local secrets are generated, not asked for. AUTH_SECRET, JWT_SECRET, SESSION_SECRET and similar are created by tokenstash, one per folder.
  • Keys can be re-checked with the provider. Keys whose provider supports an unattended check are re-checked before delivery when a check is due. A rejected key becomes a "replace this key" request; if the provider can't be reached, the key is delivered unchecked.

Where your key goes

You paste the key on a page on localhost. It is stored in your OS keychain and written to the project's .env.local, which your app loads. From delivery, the agent gets a status line such as: OPENAI_API_KEY injected to .env.local, with no key value in it. It is not a sandbox: a shell in that folder can still read .env.local.

The value goes from the page you paste it on, to your OS keychain, to the project's .env.local (mode 0600, and added to .gitignore). need and the MCP tool report back with an exit code and a line like ✓ OPENAI_API_KEY injected → .env.local, without the value, so delivering a key doesn't put it into the chat, a summary, or the conversation history. scripts/leak-test.sh runs the real binary with a canary key on every commit and fails if the canary appears on any of the surfaces it checks.

Your app loads .env.local the way it already would: frameworks such as Next.js read it on their own; for anything else, use your runtime's dotenv support, or start the program with tokenstash run --.

It's not a sandbox. An agent with a shell in an approved folder can read .env.local, just as it could without tokenstash, and anything you type into a request's note or answer field goes back to the agent as text. What goes away is the casual leak: the key pasted into chat or echoed back in a summary. The details are under Security details below and in SECURITY.md.

Using it

After tokenstash init, the agents it set up are instructed to ask tokenstash for keys. You can also run it yourself:

tokenstash need OPENAI_API_KEY RESEND_API_KEY   # write the keys you have; ask for the rest
tokenstash run -- npm run dev                   # load .env.local, and ask for a key the program says is missing
tokenstash open                                 # the inbox: everything waiting on you
tokenstash doctor                               # check the setup

Automatic, or only when you say so

By default the agent asks tokenstash on its own: init registers the MCP server and installs a skill that tells the agent to request a key whenever code needs one. If you would rather nothing happen until you ask:

tokenstash init --mode explicit

This takes the MCP server and everything the agent reads unprompted back out, and installs one command that only you can invoke: /tokenstash OPENAI_API_KEY in Claude Code and Cursor, /prompts:tokenstash OPENAI_API_KEY in Codex, /tokenstash OPENAI_API_KEY in Gemini CLI. Typed bare, it requests whatever the current task needs, and it covers the rest of that task: a key the work turns out to need later goes through it too. The command runs tokenstash need under the same rules, so the value still goes to .env.local and never into the chat. In a session where you don't type it, nothing tells the agent about tokenstash, so it will ask you for keys the way it always did. The one exception is a project where you ran tokenstash init --project: its AGENTS.md section stays, rewritten to say that keys are requested only when you invoke the command and never pasted in chat. tokenstash init --mode auto switches back, doctor shows the mode, and init --undo removes either.

Uninstall

  1. Take tokenstash out of your agents: tokenstash init --undo. It puts back the agent config files init changed, from the copies it saved at the time, and deletes the files it created. An edit you made to one of those files since init (another MCP server added to Cursor, say) is lost with it, so check them first.
  2. Optionally, remove your stored keys and data: expand the section below. Do this after step 1, since the data directory holds the copies init --undo restores from.
  3. Remove the program: brew uninstall tokenstash, or npm uninstall -g tokenstash, uv tool uninstall tokenstash, pipx uninstall tokenstash.
Remove stored keys and all data

Do this for the default data directory and for every TOKENSTASH_HOME you have used; each one has its own keychain entries.

  1. Run tokenstash list, with TOKENSTASH_HOME set for a custom home and kept set for the next commands. It shows every key in that home's index, with its identity; generated secrets like AUTH_SECRET have one identity per folder. Remove each with tokenstash forget NAME --identity IDENTITY. Then run tokenstash doctor and note the stash backend it shows.
  2. Stop any program you started with tokenstash run, close your agent sessions, then stop any tokenstash still running, for example with pkill -x tokenstash (which stops it for every home). A background inbox only exits on its own after 30 idle minutes with nothing waiting, and never if it was started with --keep.
  3. Clear what the index didn't know about: a key whose index was lost earlier is still in the OS store. Search for tokenstash in Keychain Access on macOS or Passwords and Keys (Seahorse) on Linux, and delete tokenstash's entries; their service is tokenstash for the default home, or tokenstash- and eight hex characters for a custom one. On Linux you can also run secret-tool clear service tokenstash for the default home; it skips entries in a locked keyring, so unlock it in Passwords and Keys first. If doctor showed keyutils, the keys are in the kernel keyring and are gone after a reboot.
  4. Delete the data directory: ~/.config/tokenstash on Linux ($XDG_CONFIG_HOME/tokenstash if you set it), ~/Library/Application Support/tokenstash on macOS, and any TOKENSTASH_HOME directory you used.

Keys already written into projects' .env.local files stay there until you delete them.

More

Command overview
Command What it does
tokenstash need NAME… [--blocking] Write keys to the env file, or ask for them. Exit 0 written · 10 waiting on you · 20 denied · 30 expired · 1 error
tokenstash ask "title" [--url URL] [--step STEP…] Ask you to do something only a person can do (a DNS record, a dashboard toggle, an OAuth consent screen)
tokenstash open · tasks · answer [ID] See what's waiting on you and answer it, in the inbox or from the terminal
tokenstash list · forget NAME [--identity ID] · rotate NAME Manage stored keys (never shows values)
tokenstash bind NAME --identity ID Use a different identity (say, work) for one key in this folder
tokenstash check · report-bad NAME Check keys with their providers; tell tokenstash a provider rejected one
tokenstash workspaces [list|revoke DIR|forget DIR] Which folders are approved for which keys; take a folder's approvals away
tokenstash export · import BUNDLE Move your stash to another machine in a passphrase-encrypted bundle
tokenstash run -- COMMAND Run a program with .env.local loaded; see below
tokenstash init [--mode auto|explicit] [--undo] · doctor · audit · registry Set up or remove the agent connections, automatic or on your command only; check the setup; see every delivery; list known providers
tokenstash mcp · inbox The MCP server and the inbox (started for you)

tokenstash run loads .env.local into the program's environment. If the program exits with an error and its output names a registry-known variable that isn't set, tokenstash asks for it and restarts the program once it arrives. A key requested this way needs your approval each time, even in an approved folder, since the program's output chose it.

Commands that widen what an agent can reach (approving, opening the full inbox, listing or exporting keys, and others) only run for a person at a terminal; the full list is in SECURITY.md.

Agents that speak MCP get six tools: secrets_request, secrets_list, secrets_report_invalid, human_request, task_check and task_list. The MCP server only acts for the folder your agent opened.

Configuration

config.toml lives in the data directory: ~/.config/tokenstash/ on Linux and ~/Library/Application Support/tokenstash/ on macOS, or wherever TOKENSTASH_HOME points. Every setting is optional.

Setting Default
env_file .env.local the file keys are written to, relative to the project root
inbox_port 7433 the localhost port the inbox uses
task_ttl_hours 24 how long a request stays open, and how long a denial is remembered
stash_backend auto keyring (OS keychain), keyutils (Linux kernel keyring; cleared on reboot), insecure-file (plaintext, for CI only)
notifications true desktop notifications
verify_every 24h how often a key is re-checked with its provider: <n>h, <n>m, always or never

The project is the git checkout you're in, so in a monorepo apps/web and apps/api share one .env.local at the repo root. A folder that isn't a checkout is its own project.

Over SSH or in a container: the inbox runs on the remote machine's localhost, so forward port 7433, or answer from the terminal with tokenstash answer. In a container without a keychain, set TOKENSTASH_STASH=insecure-file (plaintext) or run tokenstash on the host.

Security details
  • Delivery output never holds the key. need and secrets_request return a status without the value. tokenstash run -- is different by nature: it passes the program's own output through, with best-effort redaction of stored values.
  • What you type goes back to the agent. A note or reason on a request, and the answer to an ask, reach the agent as text. Never put a key there.
  • The link an agent prints opens one request. It can answer or decline that request and nothing else. Approving a folder needs the full inbox, which reaches you only through the desktop notification or tokenstash open.
  • Some places never receive a key: your home directory itself, /, shared temporary directories, and tool and credential directories (~/.ssh, ~/.aws, ~/.claude, …).
  • A folder that already has the value needs no approval. If its own untracked .env.local already holds the same value for a non-sensitive registry key, that delivery goes ahead without a card.
  • "A person at a terminal" is a heuristic. An agent that fakes a terminal, or a process reading your keychain as you, is outside what tokenstash can stop. It guards the line between the agent's tools and you, not between programs running as you.

What's in scope, what isn't, and how to report a problem: SECURITY.md.

What it isn't

Not a vault: for that, use 1Password or Infisical. Not a proxy: tokenstash is never in the path of your API requests. Not discovery: it never reads gh, aws, Claude Code or Codex credentials. Not a sandbox: see Where your key goes.

Adding a provider

crates/core/registry/providers.json has one entry per key: its name, the provider, the signup page, the steps, the key's format, and an optional liveness check. Pull requests welcome; see CONTRIBUTING.md and the registry verification record. How well agents actually follow tokenstash's instructions is measured in docs/agent-conformance.md.

License

MIT

Metadata

Release files for tokenstash 0.3.0

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Built distributions (wheels)

Table of built distributions (wheels) for tokenstash 0.3.0
File
tokenstash-0.3.0-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.musllinux_1_1_x86_64.whl Python 3 none Linux glibc 2.17+ x86-64, Linux musl 1.1+ x86-64 Details
tokenstash-0.3.0-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.musllinux_1_1_aarch64.whl Python 3 none Linux musl 1.1+ ARM64, Linux glibc 2.17+ ARM64 Details
tokenstash-0.3.0-py3-none-macosx_11_0_arm64.whl Python 3 none macOS 11.0+ ARM64 Details
tokenstash-0.3.0-py3-none-macosx_10_12_x86_64.whl Python 3 none macOS 10.12+ x86-64 Details

Total release size: 21.8 MB

Release files / tokenstash-0.3.0-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.musllinux_1_1_x86_64.whl

Download URL tokenstash-0.3.0-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.musllinux_1_1_x86_64.whl
Size 6.9 MB
Tags Linux glibc 2.17+ x86-64 Linux musl 1.1+ x86-64 Python 3
SHA-256 checksum
How to use checksums
a9ba9487103544ffe9c3ac06ea36daf4460912a4d9b608f0b07b79a191140157
BLAKE2b-256 checksum
How to use checksums
e83dd60bb08f3a93745b5e456356461f0b45ba8271cf510a2f1ee52d504130ed
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Sep 14, 2026.

Transparency log

Release files / tokenstash-0.3.0-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.musllinux_1_1_aarch64.whl

Download URL tokenstash-0.3.0-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.musllinux_1_1_aarch64.whl
Size 6.7 MB
Tags Linux glibc 2.17+ ARM64 Linux musl 1.1+ ARM64 Python 3
SHA-256 checksum
How to use checksums
73e54c82d3246d4aacdd4fbb973ee269df366362a45aeb920fd8d4dc75aa5fd9
BLAKE2b-256 checksum
How to use checksums
aa4ad3032d7fe361949dc32d2577e89a7583ca89a25d0960bfb3e25bfea79eb2
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Sep 14, 2026.

Transparency log

Release files / tokenstash-0.3.0-py3-none-macosx_11_0_arm64.whl

Download URL tokenstash-0.3.0-py3-none-macosx_11_0_arm64.whl
Size 4.0 MB
Tags Python 3 macOS 11.0+ ARM64
SHA-256 checksum
How to use checksums
955a141dde6f6b4d422a07fe0126e1239cd4e23cfc36278f5766c6b68440fc15
BLAKE2b-256 checksum
How to use checksums
ada2150691b769dc7bc6ca599d8f48709e4555bb1c1f4e1ef6b92d6e274703cf
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Sep 14, 2026.

Transparency log

Release files / tokenstash-0.3.0-py3-none-macosx_10_12_x86_64.whl

Download URL tokenstash-0.3.0-py3-none-macosx_10_12_x86_64.whl
Size 4.2 MB
Tags Python 3 macOS 10.12+ x86-64
SHA-256 checksum
How to use checksums
bb9d2db9e010e6b23142a86df078294f10879618c28e9ba97c9f949fe874240a
BLAKE2b-256 checksum
How to use checksums
8ded23c169a9d5316857b510f4051b0cf64e9cfccf9c3cd14d39df99a67e334a
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Sep 14, 2026.

Transparency log

Release history Release notifications | RSS feed

This release

0.3.0 This release

4 release files

0.2.0

4 release files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page