Skip to main content
Glama

🔐 keep

Use a secret without holding it. An encrypted vault whose values are injected into the commands an agent runs, and redacted from everything that comes back. The agent works with â€čkeep:GH_TOKENâ€ș. The token itself never enters its context, its transcript, or anything that reads the transcript later.

Part of tools-for-agents. Zero dependencies: the Node standard library plus your OS keychain. CLI + MCP.


Why this exists

An agent that needs an API key today has one way to get it: read it. It runs cat .env or echo $TOKEN, or it asks you to paste the key into the chat. From that moment the value sits in the model's context. It also sits in the session transcript on disk (~/.claude/projects/*.jsonl), and every process that reads transcripts can see it: summarisers, search indexes, memory systems that dream sessions into long-term memory.

Measured on the machine this was built on, on the day it was built: the first keep scan --transcripts went through 7,335 transcript files (1.28 GB) in ten seconds and found a Firebase service-account private key. An agent had read the key out of a project's .env weeks earlier, and it had been in a transcript ever since.

keep closes that path. The agent names the secret, and keep hands the value to the program, never to the agent:

keep run --with GH_TOKEN -- gh api user
keep run -- curl -H 'Authorization: Bearer {{OPENAI_API_KEY}}' https://api.openai.com/v1/models

Anything the program prints goes through a redactor built from every kept value, and the agent reads â€čkeep:OPENAI_API_KEYâ€ș wherever the value would have appeared.

Related MCP server: Keiko

Install

git clone https://github.com/tools-for-agents/keep.git && cd keep
node src/cli.js init                      # creates ~/.keep; the master key goes into the OS keychain
ln -s "$PWD/src/cli.js" ~/.local/bin/keep
claude mcp add keep --scope user -- node "$PWD/mcp/mcp-server.js"

Node 22+. Nothing to npm install.

For the person

keep set GH_TOKEN                     # typed hidden, never echoed, never in shell history
pbpaste | keep set OPENAI_API_KEY     # or piped
keep set DEPLOY_KEY --allow gh,fly    # only these programs may receive it
keep import .env                      # keep every KEY=value in a dotenv file (then delete the file)
keep list                             # names, policy, usage — and what agents have asked for
keep audit                            # every use: when, which names, which command, exit code
keep scan --transcripts               # has anything already leaked?
keep redact --patterns < in > out     # a filter: kept values and known key shapes → their names

keep redact is for the other tools that store what an agent wrote: a memory that dreams transcripts, a log shipper, a search index. ghost pipes every session through it before dreaming, so a key an agent once printed does not end up in long-term memory. With --patterns it also masks anything shaped like a well-known key, and it masks a private key as a whole block, body included, not just its BEGIN line.

For the agent (MCP)

Tool

What it does

keep_list

names, allowed commands and usage for each kept secret, never values; also the secrets you asked for that have not arrived yet

keep_run

runs argv with the named secrets injected as env vars; {{NAME}} in an argument is replaced; stdout and stderr come back redacted

keep_scan

reports where a kept value, or anything shaped like a well-known key, shows up in files or transcripts: file, line and name, never the value

keep_request

asks your person for a secret you lack; it shows up in their keep list, and is answered by keep set

keep_audit

the usage log

No tool returns a value. That is the design, not a gap in it, and a test fails if a tool whose name looks like get, read, reveal or export ever appears.

What the redactor catches

A secret leaks in more shapes than its own. curl -v prints the Authorization header, and a Basic header is base64("user:" + secret): the secret's bytes, shifted by the username and written in another alphabet. So every value becomes a set of needles:

  • the raw value, its JSON-escaped form and its URL-encoded form;

  • base64 and base64url at all three byte alignments. base64 works in 3-byte groups, so how the secret encodes depends on how many bytes came before it. keep encodes it after 0, 1 and 2 bytes of padding and keeps only the characters that depend on the secret alone, which finds it inside any base64 string.

Output is streamed, and a value can be split across two chunks. The redactor holds back (longest needle − 1) characters at each boundary. A test splits a secret at every possible position and checks all of them.

Policy

keep set NAME --allow gh,curl restricts a secret to named programs. A restricted secret:

  • cannot be handed to sh -c, node -e or python -c, which is the move an agent would make if it had been talked into exfiltrating the value;

  • must be run by name (gh) and not by path (./gh), because a file named gh in the working directory is not the gh you meant.

Honest limits

keep is built against accidental exposure (a value in a context window, a transcript or a log) and it narrows misuse. It is not a boundary against a hostile process running as you, and it says so here rather than let you find out:

  • Anything running as your user can ask the keychain for the master key, or read ~/.keep/master.key on the file backend, just as it could read your .env.

  • A program you allow can do whatever it likes with the value. --allow curl lets curl send the key to any host.

  • Redaction catches the shapes listed above. It does not catch a value that was reversed, hashed, split or re-encoded some other way before it was printed.

  • keep status says which backend holds the master key. On Linux without secret-tool it is a 0600 file next to the vault, which is weaker, and keep prints that when the vault is created.

Files

~/.keep/secrets.enc

the values: AES-256-GCM, one authenticated blob. If it has been altered it fails to open; it never decrypts to garbage

~/.keep/index.json

names, allow-lists, usage counts, requests. Never a value

~/.keep/audit.jsonl

one line per use, with the command already redacted

OS keychain · tools-for-agents.keep

the 32-byte master key (macOS security, Linux secret-tool, otherwise ~/.keep/master.key)

Every file is 0600 in a 0700 directory, and each write goes to a temp file first and is renamed into place, so a crash never leaves half a vault.

Env

KEEP_HOME

vault directory (default ~/.keep); always redirect it in tests

KEEP_BACKEND

keychain · secret-tool · file

KEEP_TRANSCRIPTS

where --transcripts looks (default ~/.claude/projects)

Test

node --test               # 23 tests; the file backend, no keychain touched
node scripts/mutants.mjs  # breaks each safety property on purpose and demands the suite goes red

License

MIT

Related MCP Connectors

Related MCP Servers

  • A
    license
    Not graded
    quality
    C
    maintenance
    Enables secure credential storage for AI agents by encrypting secrets and providing agent-invisible references, ensuring sensitive data never leaks to the model.
    MIT
  • F
    license
    Not graded
    quality
    C
    maintenance
    Enables AI agents to securely use secrets by running commands with environment-injected credentials and sanitizing output to prevent leakage.
    1
    -
  • A
    license
    Not graded
    quality
    C
    maintenance
    Enables deterministic, no-LLM scrubbing of secrets from text or logs before they enter agent context, supporting redaction, masking, and hashing without ever leaking the original value.
    MIT
  • A
    license
    C
    quality
    C
    maintenance
    Enables AI agents to securely store and manage encrypted secrets locally while using them indirectly through environment-variable injection or file writes, so plaintext values never enter the agent's context.
    8
    MIT