# key-amnesia
[](https://github.com/fujitoid/key-amnesia/releases/latest)
[](https://pypi.org/project/key-amnesia/)
[](https://pypistats.org/packages/key-amnesia)
[](https://github.com/fujitoid/key-amnesia/actions/workflows/tests.yml)
[](https://github.com/fujitoid/key-amnesia/stargazers)
[](https://github.com/fujitoid/key-amnesia/blob/master/LICENSE)
[](https://github.com/fujitoid/key-amnesia/blob/master/CHANGELOG.md)
[](https://github.com/fujitoid/key-amnesia/wiki)
[](https://discord.gg/4WnQfk49xX)
**Let your AI agent *use* your passwords and API keys — without ever letting it *see* them.**

## The problem is `.env`
`.env` was designed for a threat model whose adversary was **git**. One line in `.gitignore` and you were done. That model is obsolete: the adversary is now the **agent sitting in your project**. Anything the agent can read — `.env`, shell history, MCP configs, a credentials JSON left in the tree — is a LEAK (Locally Exposed Agent Key). Pasting a key into chat is worse; it lives in the conversation forever.
Your choices used to be ugly: paste the key, leave it in plaintext where the agent can read it, or do that part yourself.
**key-amnesia is the fourth option.** Secrets live in an encrypted vault. The agent triggers commands that *use* them — values are injected into the child process environment, out of the agent's sight. If a command prints a secret, key-amnesia censors it before the agent sees the output. The master password can only ever be typed by you, at a real keyboard: when an agent needs approval, a **separate console window** pops up — one the agent cannot read or type into. Auth routing requires both stdin and stdout to look like a TTY before prompting inline; set `KEY_AMNESIA_NONINTERACTIVE=1` in agent harnesses to always force that window.
The agent gets amnesia. That's the whole point.
**Docs:** [github.com/fujitoid/key-amnesia/wiki](https://github.com/fujitoid/key-amnesia/wiki) — or run `ka docs` (prints the URL; opens a browser unless you pass `--print`).
## How it works, in 30 seconds
```bash
pip install key-amnesia
ka setup # skills + secret-guard hook for Claude Code / Cursor / Codex
ka init --project # or: ka init for a global vault
ka import .env # move plaintext into the vault (TTY-only; never prints values)
ka scan # find remaining LEAKs (names/paths only)
ka run --secret API_KEY -- python my_script.py
```
`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 `ka run` and your approval is needed, a new console window appears with a clear message — only your password, typed there, lets it proceed. Close the window to deny.
## Install
```bash
pip install key-amnesia
```
Or from source: `pip install git+https://github.com/fujitoid/key-amnesia`, or from a local clone: `pip install .` — every path gets you both the full `key-amnesia` command and the short `ka` alias.
> Windows and Linux supported. macOS isolated-console spawn is **experimental** (PID-file wrapper around Terminal.app / osascript) until a real Mac user confirms the visible window path — see [wiki — macOS](https://github.com/fujitoid/key-amnesia/wiki/macOS).
### Agent bootstrap
Paste this into your coding agent when you want it to install and teach itself key-amnesia:
```
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 skills to `~/.claude/skills/`, `~/.cursor/skills/`,
`~/.agents/skills/` (Codex), and `~/.codex/skills/` (legacy Codex /
`$CODEX_HOME`), and merges a PreToolUse / preToolUse hook that blocks tool
calls containing inline credential-shaped tokens. Codex also needs you to
review and trust the new hook via `/hooks` before it will run.
## 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; a background guard keeps the vault open for 30 minutes (configurable). Agent commands run without prompts until it expires or you `ka lock`. |
```bash
ka config set session-mode cached
ka unlock # see flags below
ka lock
```
**Unlock admission flags** (both opt-in; never the default):
| Flag | What it does |
|------|----------------|
| *(default)* | First unrecognized client gets a y/N prompt on the guard TTY. Approval admits **that connecting process** only. Real OS *descendants* of it are silent later; **siblings** under a shared parent (typical next `ka` from the same shell/IDE) are not — they re-prompt. |
| `--pre-admit` | Auto-admits the **very next** connecting process for a bounded window (no prompt). Arrival-time grant — whoever connects first. Optional `--pre-admit-secret NAME` scopes it. |
| `--admit-tree` | At the first unrecognized-peer prompt, pick a kernel-verified **ancestor** as the admission root. Admits that ancestor **and its OS descendants** for the rest of the session. Use this when the connecting client is a short-lived `ka` and you want later sibling invocations under the same parent to stay silent. Does **not** admit processes outside that root's subtree (including siblings *of the root*). Distinct from `--pre-admit` (lineage vs who arrives next). |
```bash
ka unlock --admit-tree # choose parent/IDE as root when prompted
ka unlock --pre-admit # next client only, no ancestry picker
```
Full command reference, project vaults, manifests, roles/export, and admission details: **[the wiki](https://github.com/fujitoid/key-amnesia/wiki)** (`ka docs`).
## 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. **Windows peer identity is weaker than Linux.** Linux binds admission to `SO_PEERCRED` (kernel-verified at accept) and rejects a peer whose kernel uid differs from the guard's `geteuid()`. Windows uses `GetNamedPipeClientProcessId` then an immediate `OpenProcess` whose handle is **held for the admission lifetime** so that PID cannot be recycled while admitted — but the residual race between those two calls is not eliminated, and process-tree ancestry is a consent UX (real OS descendants of an admitted root), not an airtight boundary against in-tree malware that already shares your account.
8. **The master password never crosses any inter-process channel**, in any form — it's consumed only inside the process that prompted you for it.
9. **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.)
10. **`--pre-admit` is an explicit, opt-in trust-widening you ask for.** It auto-admits whichever process happens to connect first within the window — not necessarily the one you meant — so only use it right before the command you're expecting, for a short window, and treat the loud confirmation line + audit log entry as the evidence of what it actually admitted.
11. **`--admit-tree` is a separate opt-in trust-widening (also never the default, no config/env).** At the first unrecognized-peer prompt it lets you pick a kernel-verified *ancestor* as the admission root, so every real OS descendant of that root (including later sibling CLI invocations under the same parent) is silently in-tree for the rest of the session. That is wider than admitting the short-lived connecting `ka` process alone — use it only when you intend lineage trust, and read the loud `via=interactive-tree` announce + audit line for the root you actually chose. It does **not** change `--pre-admit` (arrival-time grant vs lineage root).
12. **A live guard session reloads on change, not on a fixed schedule.** The guard checks a cheap content fingerprint of the vault file on every `run`/`list`/`status`; when another terminal changes it, the guard re-opens with the SecretBox key it already derived at unlock — no new password prompt. The tradeoff: the guard keeps that **derived key** in memory for the session. Detail: [DESIGN.md](DESIGN.md) and the [threat-model wiki page](https://github.com/fujitoid/key-amnesia/wiki/Threat-model).
13. **Runner role is not a cryptographic ACL against you.** If your local identity is enrolled as `runner`, `ka` refuses `reveal`/`copy` — effective against an agent. Anyone who knows the master password can still decrypt the vault offline. Per-member `ka export` ciphertext *is* cryptographic (only that member's key opens it).
Longer honesty notes and policy-vs-crypto labels: [wiki — Threat model](https://github.com/fujitoid/key-amnesia/wiki/Threat-model) (draft; maintainer judgement flagged).
## Community
Questions, bugs, and ideas: [Discord](https://discord.gg/4WnQfk49xX), [GitHub Discussions](https://github.com/fujitoid/key-amnesia/discussions), or [GitHub issues](https://github.com/fujitoid/key-amnesia/issues).
## Support
key-amnesia is free and open source. If it's useful to you, you can support its development here:
## Development
```bash
pip install -e ".[dev]"
pytest
```
Design rationale, file formats, invariants: [DESIGN.md](DESIGN.md). Wiki drafts suitable for publishing live in [`wiki/`](wiki/).
## License
Apache License 2.0 — see [LICENSE](LICENSE).