Skip to main content

Encrypted secret vault with human-prompt routing and output scrubbing

Project description

key-amnesia

tests

Let your AI agent use your passwords and API keys — without ever letting it see them.

key-amnesia — the vault hands the agent a sealed envelope it cannot open

AI coding agents (Claude Code, Cursor, Codex) are incredibly useful — right up until they need an API key. Then your choices are ugly: paste the key into the chat (now it lives in the conversation forever), put it in a plain-text .env file the agent can read, or just do that part yourself.

key-amnesia is the fourth option. Your secrets live in an encrypted vault. The agent can trigger commands that use them — but the actual values are injected directly into the command's environment, out of the agent's sight. If a command tries to print a secret, key-amnesia censors it before the agent sees the output. And the master password can only ever be typed by you, a real human, at a real keyboard: when an agent needs your approval, a separate console window pops up on your screen — one the agent cannot read or type into.

The agent gets amnesia. That's the whole point. And every access attempt — allowed or denied — is written to an audit log you can review.

The part that matters more than any single integration: MCP connectors and vendor plugins only ever cover the popular APIs someone bothered to build for. The long tail — a random SaaS's REST API, an internal tool, plain SMTP — is never coming. With key-amnesia, your agent can write itself a script against any API that takes a key, run it through ka run, and you don't have to be afraid anything leaks. Not "we integrated with X" — a general-purpose unlock for the APIs no one will ever get around to wrapping.

How it works, in 30 seconds

# 1. Create the vault (type the master password twice to confirm)
ka init

# 2. Store a secret (you type it once, hidden, into a password prompt)
ka set OPENAI_API_KEY

# 3. The agent runs commands THROUGH key-amnesia instead of holding the key:
ka run --secret OPENAI_API_KEY --as OPENAI_API_KEY -- python my_script.py

# 4. That's it. The script gets the real key in its environment.
#    The agent sees the script's output — with any leaked key censored:
#    "Bearer ***REDACTED(OPENAI_API_KEY)***"

ka init asks for the master password twice; if the entries do not match, nothing is created. There is no recovery if you forget that password — Argon2id + SecretBox leave none by design.

When the agent triggers step 3 and your approval is needed, you'll see a new console window appear with a clear message — "An agent-driven command is requesting: run with secret OPENAI_API_KEY" — and only your password, typed there, lets it proceed. Close the window to deny. Nothing the agent controls can type into that window.

Install

pip install git+https://github.com/fujitoid/key-amnesia

Or from a local clone: pip install . — either way you get both the full key-amnesia command and the short ka alias.

Windows and Linux supported; macOS still falls back to fail-closed (not yet implemented).

Agent bootstrap

Paste this into your coding agent when you want it to install and teach itself key-amnesia (not a human pip walkthrough):

Install key-amnesia and set yourself up to use it correctly for secrets in
this project:
1. pip install key-amnesia
2. Verify `ka --version` works in a fresh terminal (if not found, fix PATH
   as instructed).
3. Run `ka setup` (installs its skills + safety hook globally).
4. Tell me to restart this session so the skill loads, then tell me exactly
   what to do in my OWN terminal to finish setup (master password etc.) —
   you cannot do that step yourself.

ka setup copies the bundled key-amnesia-usage, key-amnesia-hygiene, and key-amnesia-migrate skills to ~/.claude/skills/ and ~/.cursor/skills/, and merges a PreToolUse (Claude Code) / preToolUse (Cursor) hook into each host's own config that blocks tool calls containing inline credential-shaped tokens. Restart or reload the host afterward to pick both up.

Two modes: ask every time, or unlock a session

Mode What it feels like
per-call (default) Every use of a secret asks for your password. Maximum safety, maximum prompts.
cached You run ka unlock once in your terminal; a background "guard" keeps the vault open for 30 minutes (configurable). Agent commands run without prompts until it expires or you run ka lock.
ka config set session-mode cached   # switch (asks for your password)
ka unlock                           # start a session
ka lock                             # end it early, any time

ka unlock runs the guard in that terminal — it's the same window for the life of the session. The startup line tells you when it expires and how to stop it early (Ctrl+C or ka lock from another terminal); a periodic nudge repeats that even if the guard sits idle the whole time. Before it expires, the guard asks right there whether to extend. No answer means it locks itself. The first command any client sends to a live guard also gets a one-time yes/no admission prompt in that same window (Session (pid ...) wants: ... Admit? [y/N]) — approve once and the rest of that session's commands go straight through.

Commands

Command What it does
ka init Create an empty vault (type master password twice; refuse if already exists)
ka passwd / ka change-password Change the master password (re-encrypts the vault with a fresh salt; refuses while a session is active)
ka set NAME Store or update a secret (value typed hidden; password required; vault must already exist)
ka remove NAME Delete a secret (password required)
ka run --secret NAME --as ENV_VAR -- <command> Run a command with the secret injected; output censored. The agent-facing command.
ka list Show secret names only — never values; safe for agents, no prompt
ka unlock / ka lock Start / end a cached session
ka reveal NAME Show a value to you (password required every time, even mid-session)
ka copy NAME Copy a value to your clipboard instead of showing it (same rule)
ka config show / ka config set KEY VALUE View / change settings (changes require your password)
ka status Is a session active, and until when — plus, if not, what happened to the last one
ka setup Install agent skills + the secret-guard hook for Claude Code / Cursor (--skills-only / --hook-only)

Every command supports --help.

reveal and copy deserve a special note: even if an agent invokes them, the value appears only in the pop-up window on your screen (or your clipboard) — the agent's own process receives nothing but a status flag. And they always require a fresh password, session or no session — so an agent can never ride an open session into actually reading a value.

Under the hood

For the security-curious — the full detail lives in DESIGN.md:

  • Encryption: the vault is a single file sealed with XSalsa20-Poly1305 (libsodium's SecretBox) under a key derived from your master password via Argon2id at its most expensive (SENSITIVE) setting — deliberately slow to brute-force, and deliberately never dialed down.
  • The routing rule: any command needing your password checks whether it's running in a real terminal. Yes → asks right there. No (an agent invoked it) → spawns a fresh, isolated console window whose keyboard input can only come from you. No interactive session at all → fails closed, never falls back to something insecure.
  • The guard never hands out secrets. In cached mode, the guard itself runs your command with the secret injected and returns only the censored output and exit code. Its protocol simply has no "give me the value" request — so even another process connecting to it directly can't ask for one. Guard verbs stay exactly run / list / lock / status / renew.
  • Nothing sensitive on command lines. Windows records process command lines in its audit logs (event 4688); key-amnesia passes all sensitive hand-off data between its own processes via environment variables instead.
  • Audit log: ~/.key-amnesia/audit.log, append-only JSON lines — timestamp, action, secret names (never values), route, allowed/denied/timeout.
  • Admission consent: the first command any client sends to a live guard triggers a one-time yes/no prompt in the guard's own terminal window; approve once and it's remembered (an opaque token, not a password) for the rest of that session. This sits on top of — never replaces — the hard guarantee above.
  • Honest death reporting: ka lock / ka status tell you what actually happened to the last session (locked, expired, interrupted, or crashed: <reason>) instead of a bare "no active session."

Files live in ~/.key-amnesia/ (override: KEY_AMNESIA_HOME, KEY_AMNESIA_VAULT_PATH).

Security limits — read this part honestly

No tool in this class can promise absolute secrecy, and we'd rather tell you exactly where the edges are:

  1. A command you run can still leak its own secret. Censoring catches exact copies of the value in output — a command that base64-encodes or otherwise obfuscates the secret before printing slips through. This limit is shared by every tool of this kind (op run, teller run).
  2. Output is not live. Command output is collected fully, censored, then released — the agent sees it only after the command finishes.
  3. Secret names are stored in plain text (so ka list can work without a password). Values never are. Treat names as non-sensitive labels.
  4. The pop-up window assumes the agent can't control your screen. If you've given an agent screen-reading and keyboard/mouse-injection powers, the window's isolation weakens — your typed password stays hidden, but a yes/no confirmation could theoretically be clicked by such an agent. The same caveat applies to the guard's admission prompt.
  5. Headless machines fail closed. No display → no way to approve → the operation is denied. By design.
  6. Same-user processes share your privileges. Any program running under your OS account can talk to a live guard session (this is equally true of ssh-agent). That's why the guard is designed to never return raw values — the worst a rogue same-user process gets is the same bounded "run a command" capability the legitimate path has, and even that requires one admission prompt to be approved on your own screen first.
  7. The master password never crosses any inter-process channel, in any form — it's consumed only inside the process that prompted you for it.
  8. Avoid ka set NAME VALUE with the value inline. It's supported for scripting, but an inline value briefly appears on the calling process's command line — visible to same-user process inspection and Windows command-line auditing. Prefer plain ka set NAME and type the value at the hidden prompt. (If an agent tries the inline form, the approval window shows you the incoming value before asking for your password — so you can still deny it.)
  9. A live guard session can serve a stale secret. The guard decrypts the vault once, at ka unlock time, and holds that snapshot in memory for its whole session — it does not notice a ka set/ka remove made against the vault file while it's already running. ka run/ka list against a live guard will keep returning the pre-update value until you cycle the session (ka lock then ka unlock). Not a leak — the guard never hands out anything it wasn't already holding — but a real staleness gap worth knowing about if you rotate a secret mid-session.

CLI appearance

On a real terminal, status lines use a restrained brushed-chrome palette (cool chrome-blue for info/success, warm brass for warnings, red only for hard denials, slate for secondary/supporting detail lines like the auth prompt's secrets list). Set NO_COLOR or redirect output to a pipe/file and all ANSI escapes are omitted — agent-facing and scrubbed paths stay plain text. Unicode glyphs (✅ ❌ 🔒 🔓 ⏳ 💀) fall back to ASCII ([OK] / [DENIED] / [LOCKED] / [LISTENING] / [EXPIRED] / [CRASHED]) when color or unicode is unavailable. Scrubbed command output and raw revealed secret values are never styled.

Development

pip install -e ".[dev]"
pytest

Design rationale, file formats, invariants: DESIGN.md.

License

Apache License 2.0 — see LICENSE.

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

key_amnesia-0.3.2.tar.gz (83.6 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

key_amnesia-0.3.2-py3-none-any.whl (58.4 kB view details)

Uploaded Python 3

File details

Details for the file key_amnesia-0.3.2.tar.gz.

File metadata

  • Download URL: key_amnesia-0.3.2.tar.gz
  • Upload date:
  • Size: 83.6 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.13.14

File hashes

Hashes for key_amnesia-0.3.2.tar.gz
Algorithm Hash digest
SHA256 d7a12ae1daa3471fee2e26089c14ac085e8f6496301724ec44727c836dc68e0c
MD5 d6a08563a56103fffc49169698f3affa
BLAKE2b-256 b9fe5f5eab532352320d809dd2c7392fecc20ad0ecaf6a1b7c5946c552e52587

See more details on using hashes here.

File details

Details for the file key_amnesia-0.3.2-py3-none-any.whl.

File metadata

  • Download URL: key_amnesia-0.3.2-py3-none-any.whl
  • Upload date:
  • Size: 58.4 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.13.14

File hashes

Hashes for key_amnesia-0.3.2-py3-none-any.whl
Algorithm Hash digest
SHA256 b6952e3b6cbcf0047bf6d6a050e8bda7e4909b0acb19ad08169c899e9162e046
MD5 9772d183d7c8831f757abf63e935ec09
BLAKE2b-256 d0ccd13f3447ac401e71ad854f5cacced2d9a54e65f8490eb333b2a29324fc32

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page