Skip to main content
Pre-release

This release is a pre-release and may not be stable for production use.

tokenstash

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

tokenstash is a local credential broker for coding agents. The agent runs one command instead of stalling on Please paste your API key. A key you already have is written into the project's env file once you have approved that directory; a key you don't have gets you a link and a desktop notification. What the agent gets back is an exit code and a status line, not the value.

Download · Provider registry · Security · Changelog

Rust MIT MCP Keychain Providers Tests Telemetry

Before and after

Without tokenstash, every new project stalls on the same line, and the key you paste lands in the chat transcript:

Please provide: OPENAI_API_KEY= RESEND_API_KEY= TAVUS_API_KEY=

With it, the agent asks tokenstash instead:

agent › tokenstash need OPENAI_API_KEY RESEND_API_KEY TAVUS_API_KEY
      ✓ OPENAI_API_KEY injected → .env.local
      ✓ RESEND_API_KEY injected → .env.local
      ⏳ TAVUS_API_KEY pending — task t_7fa2 → http://127.0.0.1:7433/…

The two keys you already had were written to .env.local (this directory had been approved for them). The third is new: you click the link, follow it to Tavus's own key page, paste once, and the agent resumes. The next project that needs TAVUS_API_KEY shows you one card naming the directory and the file; approve it and that directory is silent from then on.

Install

macOS and Linux. Pick one:

npm install -g tokenstash          # or: bun add -g tokenstash · pnpm add -g tokenstash
brew install kgarg2468/tokenstash/tokenstash
uv tool install tokenstash         # or: pipx install tokenstash
cargo install --locked --git https://github.com/kgarg2468/tokenstash tokenstash

Then:

tokenstash init

init picks a keychain backend and registers the MCP server with the agents it finds (Claude Code, Codex, Cursor, Gemini CLI). Claude Code also gets a skill file and Codex an AGENTS.md snippet, so those two reach for it unprompted; anything that can run a shell command can use the CLI. tokenstash init --undo takes every registration back out.

Prebuilt binaries for macOS (arm64, x64) and Linux (x64, arm64; static, any distribution) with sha256 sidecars are attached to the latest release. The npm package is a launcher plus one binary package per platform (optionalDependencies, no install scripts). The PyPI wheels carry the same binary and no Python code. Windows is not supported yet.

How it works

Step What you do What tokenstash does
Ask The agent needs a key it has never seen Files a task, sends a desktop notification, prints a localhost link for the chat that opens that one card
Paste You open the vendor's page and paste once Checks the key's format, probes the provider where the registry supports it, stores the value in the OS keychain
Inject Nothing: the agent re-runs the same command Writes the key to .env.local, mode 0600, .gitignore enforced, exit 0
Reuse A different project, a different agent, next week Shows one pairing card the first time that directory asks for stored keys: exactly which keys go into which file. Allow these, Allow these + any non-sensitive key here, or Deny (remembered for a day). Silent there afterwards
  • Nothing is trusted by folder. A paste grants one key to one directory. Keys tagged sensitive (live Stripe, AWS, service-role keys, deploy and package-registry tokens) and keys the registry does not know get their own card per directory; the broad button never covers them. A directory is recognised by its path plus a fingerprint taken when it was paired; a directory deleted and re-created at the same path pairs again when the fingerprint changes (on filesystems without birth time, inode reuse can escape detection). A paste from an agent's link is refused while another directory already holds a grant that would receive it; a grant given later can still deliver a value pasted earlier, since values carry no record of who pasted them. A copy that already carries the same value in its own untracked .env.local needs no card for non-sensitive registry keys. Nothing is delivered into /, your home, /tmp, tool or credential directories (~/.ssh, ~/.aws, ~/.claude, …) or the directory holding the stash itself.
  • You create every account. tokenstash gets you to the right page with the right steps; it never signs up for you, never proxies an API, never reads another tool's credential store.
  • Local secrets are generated, not requested. AUTH_SECRET, JWT_SECRET, SESSION_SECRET and friends are created by tokenstash, one per directory, and never involve a human. If two directories must share one, paste it into both.
  • Eligible keys are re-checked when due. A key whose registry entry allows an unattended probe, unchecked for longer than verify_every and under no lease or backoff, gets one free, read-only request to its provider before delivery, when verification is enabled for that key and the request's probe budget permits. A dead key becomes a "Replace" card; a provider outage delivers the key unchecked. Keys without such a probe are delivered as stored.
  • The project is the git root. need writes the env file at the nearest checkout you own, so in a monorepo apps/web and apps/api share repo/.env.local. A directory that is not a checkout is its own project.

What it protects, and what it does not

The claim is narrow on purpose. A normal credential delivery (need, or the secrets_request MCP tool) writes the value to the env file and returns status: an exit code and a line naming the key and the file, without echoing the value. The value goes from your paste to the OS keychain to the env file. tokenstash run -- is different: it forwards the child program's own output, with best-effort redaction of exact stored values that a program printing a key in fragments defeats. Notes you type on a card (a denial reason, a text answer) are returned to the agent as text; they are not a place to paste a credential, and the check that refuses a note that looks like one is a heuristic. scripts/leak-test.sh drives the real binary with a canary and fails the build if the canary appears on any surface it exercises; it proves those surfaces, not every path a future change could add.

What it does not do:

  • It is not a sandbox. An agent with a shell in an approved directory can read that directory's env file. Delivery is that file. Processes running as your user are not isolated from each other: pairing is keyed by a directory's path and fingerprint, and says nothing about what another same-user process does there. These checks assume filesystem paths remain stable during sessions and delivery; concurrent path replacement is outside this protection. The skill file and MCP guidance tell agents to load the file with the runtime rather than read it into context, and the conformance harness checks whether they comply, but nothing enforces it. tokenstash removes the casual leak (the paste into chat, the key echoed in a summary), not the deliberate read.
  • Loopback is not authentication. Every inbox page and task action needs a credential. The one open route, /verify, answers an ownership challenge with a proof key that is never placed in a URL, cookie or form and is separate from the browser session. The link an agent prints opens one card and can answer or decline that card, whatever config.toml says; it stays restricted even in a browser that already holds the full session, so to approve a card you open the full inbox (tokenstash open, or the desktop notification) and select the card there rather than reloading the scoped page. The browser session is minted fresh each time the inbox starts, so a session captured before a restart stops working. Your notification history is therefore as trusted as your terminal (notifications = false turns it off). A bare loopback URL carries no proof of who is listening: tokenstash's own surfaces refuse to send you to an unverified listener, but if something else holds the port when you click an old link, the page you see is theirs. Details in SECURITY.md.
  • "Person at a terminal" is a heuristic. Commands that widen reach (answer --allow, open, forget, list, audit, workspaces, export, import, and the agent registration in init) refuse unless both standard streams are a terminal and no agent marker is set. An agent that allocates a pseudo-terminal and scrubs its environment, or that reads your keychain and config as your user, is outside what tokenstash can stop. It defends the line between the agent's tools and you, not the line between processes running as you.

The full list of guarantees, what is out of scope, and how to report something: SECURITY.md.

Upgrading from 0.1

Existing per-project approvals become grants automatically, for directories that still exist and were not re-created since. trust_roots in config.toml stop applying, so a project that was silent only because of a root shows one pairing card (unless its .env.local already holds the value); tokenstash trust rm DIR tidies the old list. need and ask lost --project (the directory you run them in is the project), the MCP tools lost their project argument, and tokenstash workspaces replaces trust. A 0.1 binary can still open the upgraded database. Details in CHANGELOG.md.

Configuration

config.toml lives in ~/.config/tokenstash/ (macOS: ~/Library/Application Support/tokenstash/), or wherever TOKENSTASH_HOME points. Every key is optional.

key default
env_file .env.local the file keys are written to, relative to the project root; one setting for every project
inbox_port 7433
task_ttl_hours 24 how long a card stays open, and how long a denial is remembered
stash_backend auto keyring (OS store), keyutils (Linux kernel keyring: survives logout, not reboot), insecure-file (plaintext, 0600, warns on every run; CI only). TOKENSTASH_STASH overrides it
notifications true desktop notifications; they carry the full inbox session
inbox_links paste legacy; accepted and ignored. The link agents print is always scoped to one card; full used to make it carry the full session and now only warns. The full inbox is tokenstash open or the notification
verify_every 24h <n>h, <n>m, always (still at most once a minute per key), never

No browser, no desktop? Over SSH the inbox is on the remote's loopback: forward port 7433 and run tokenstash open there for the full inbox, or answer from the terminal with tokenstash answer. In a container with no keyring, set TOKENSTASH_STASH=insecure-file (plaintext) or run tokenstash on the host.

Uninstall: tokenstash init --undo removes what init wrote outside its own directory; tokenstash forget NAME removes a key from the keychain; delete the config directory for the rest.

Commands

tokenstash need NAME… [--why] [--url] [--step …] [--blocking] exit 0 injected · 10 pending · 20 denied · 30 expired · 1 error. --force re-asks after a denial and is for a person at a terminal
tokenstash ask "title" [--url] [--step …] [--expects confirm|text] non-secret human task (DNS, dashboard toggle, OAuth consent)
tokenstash answer [id] [--stdin] [--allow] [--deny] answer from the terminal instead of the inbox
tokenstash tasks [--all] [--history] · tokenstash open what is waiting on you
tokenstash list · forget NAME · rotate NAME · bind NAME --identity work manage the stash (never shows values); bind after the directory has paired
tokenstash check · report-bad NAME --status 401 prove keys are live; tell tokenstash when a provider rejects one
tokenstash export · import passphrase-encrypted bundle, to move a stash between machines
tokenstash workspaces [list|revoke DIR|forget DIR] which directories are paired with which keys; take a directory's grants away (values already written stay). For a person at a terminal
tokenstash run -- npm run dev zero-config shim: dies on a missing registry-known key → asks → restarts. Every key a program's output asks for gets its own yes, each run, never a standing grant
tokenstash init [--undo] · mcp · inbox · doctor · audit · registry

MCP tools, for agents that speak it: secrets_request, secrets_list, secrets_report_invalid, human_request, task_check, task_list. The server binds the one directory your agent opened and refuses to act for any other.

What it is not

Not a vault: use 1Password or Infisical; backends for them are a later step. Not a proxy: tokenstash is never in the request path, and no traffic of yours flows through it. Not discovery: it never reads gh, aws, Claude Code or Codex auth state. Not a sandbox: see above.

Adding a provider

crates/core/registry/providers.json has one JSON object per key: name, provider, signup URL, ordered steps, key pattern, optional liveness check. PRs welcome; that file is the whole product's breadth. See CONTRIBUTING.md and the verification record in docs/registry-verification.md, reproducible with scripts/verify-registry.py. Whether agents actually follow the skill file is measured by docs/agent-conformance.md.

License

MIT

Metadata

Release files for tokenstash 0.2.0rc1

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.2.0rc1
File
tokenstash-0.2.0rc1-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.2.0rc1-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.musllinux_1_1_aarch64.whl Python 3 none Linux glibc 2.17+ ARM64, Linux musl 1.1+ ARM64 Details
tokenstash-0.2.0rc1-py3-none-macosx_11_0_arm64.whl Python 3 none macOS 11.0+ ARM64 Details
tokenstash-0.2.0rc1-py3-none-macosx_10_12_x86_64.whl Python 3 none macOS 10.12+ x86-64 Details

Total release size: 21.5 MB

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

Download URL tokenstash-0.2.0rc1-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.musllinux_1_1_x86_64.whl
Size 6.8 MB
Tags Linux glibc 2.17+ x86-64 Linux musl 1.1+ x86-64 Python 3
SHA-256 checksum
How to use checksums
b7185246534db78756494cecf5bd40fb86b19e2c51d6a95bf0702fb71258a87f
BLAKE2b-256 checksum
How to use checksums
ac1bb3f6a1cad43a2b05aea6d786eb8d9ec3930a403dc2b9ce98f5f5862393b8
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 10, 2026.

Transparency log

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

Download URL tokenstash-0.2.0rc1-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.musllinux_1_1_aarch64.whl
Size 6.6 MB
Tags Linux glibc 2.17+ ARM64 Linux musl 1.1+ ARM64 Python 3
SHA-256 checksum
How to use checksums
64e2b23be6ac4eb3832da40ff6d5948cbfabb6a3b80a8d29f874860d54a91171
BLAKE2b-256 checksum
How to use checksums
26613b7e6e15c4739197777205ac1d256cd6c74dee8035791b4d23a18e28ebb4
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 10, 2026.

Transparency log

Release files / tokenstash-0.2.0rc1-py3-none-macosx_11_0_arm64.whl

Download URL tokenstash-0.2.0rc1-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
1b0a533138e9b2b27ae23c1493e02d1b65a950469ffc666133d1c25d6915f5bd
BLAKE2b-256 checksum
How to use checksums
061490655c45b593883b61d7a21c185c57726b5387945782af7b24712195c09b
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 10, 2026.

Transparency log

Release files / tokenstash-0.2.0rc1-py3-none-macosx_10_12_x86_64.whl

Download URL tokenstash-0.2.0rc1-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
57337d569aedb0946e3b2e4e084543e4eedb381b163828add963b28530902da2
BLAKE2b-256 checksum
How to use checksums
f68aa4ed91527b27600b8c1cb92d899359571d918ba27d314212a0f2e5315c47
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 10, 2026.

Transparency log

Release history Release notifications | RSS feed

0.3.0

4 release files

0.2.0

4 release files

This release

0.2.0rc1 This release

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