Keep secrets out of your coding agent (5 minutes)
Put your keys in an encrypted vault on your machine, so your coding agent works with markers instead of real values. Vault, .env, hooks, commands, APIs and MCP configs.
zn-gate keeps secret values out of what your coding agent sends to its model. The agent works with markers such as __ZN_GITHUB_TOKEN__; the real values stay in an encrypted vault on your machine and are filled in only where you allowed them. Everything runs locally and needs no account.
This works with Claude Code, Codex CLI, Cursor (IDE) and OpenCode. What each agent can and cannot do is in Supported agents; the limits are in the threat model.
1. Install and unlock the vault
npm install -g zn-gate
zn unlock
On Windows PowerShell, npm and npx can fail with "running scripts is disabled on this system". Use npm.cmd and npx.cmd instead, run the commands from Command Prompt, or allow local scripts for your account with Set-ExecutionPolicy -Scope CurrentUser RemoteSigned. More in Install on Windows.
The first zn unlock creates the vault: an age identity kept in your system keychain (macOS Keychain, Windows Credential Manager, or Secret Service on Linux), or behind a passphrase when there is no keychain. The vault stays unlocked for 8 hours, or until 30 minutes without use, or until you run zn lock.
After you update zn-gate, a vault that is still running from the older version keeps secrets as markers in the agent hooks until it restarts. zn vault status shows the version of the running vault and flags an outdated one, and zn unlock restarts it for you.
zn asks you to confirm some things outside the agent (step 4). On a desktop it shows a system dialog. On a server or over SSH there is no dialog: run the vault in a terminal of its own and answer there.
zn unlock --foreground
2. Move your .env values into the vault
zn env shadow .env
Every sensitive value moves into the vault and .env keeps only markers:
DATABASE_URL=postgres://app:__ZN_DATABASE_URL_PASSWORD__@db:5432/app
STRIPE_KEY=__ZN_STRIPE_KEY__
PORT=3000
An encrypted copy of the original is kept, and zn env unshadow restores it. To add a single secret, type it at a hidden prompt (never as an argument):
zn secret set GITHUB_TOKEN --host api.github.com
3. Install the hooks for your agent
zn hooks install claude-code # or: codex, cursor, opencode
From now on, every file read, command output and pasted prompt goes through the scrubber before it reaches the model. Known values, more than 60 secret formats (cloud keys, tokens, private keys, connection strings) and PII that passes a checksum are replaced with tokens. See Redaction for the details.
What the hooks block:
- Reads of
.envfiles that hold real values. - Credential files such as
~/.aws/credentials,~/.ssh/id_*and~/.npmrc, and zn's own~/.znfolder. - A prompt that contains a secret: it is not sent. In prompts, zn also blocks a value of 8 characters or more given to a credential name, even in the middle of a sentence (
my key is STRIPE_KEY=...,DB_PASSWORD=...). It lets through a placeholder (changeme123,<key>), a reference (${API_KEY},__ZN_STRIPE_KEY__) or code. Store the value withzn secret set NAMEand write__ZN_NAME__in the prompt instead.
Codex runs new hooks only after you trust them: open Codex and use /hooks.
On a computer managed by an organization, Claude Code can be set up to ignore the hooks and MCP servers you install yourself (disableAllHooks, allowManagedHooksOnly, managed-mcp.json, or an MCP allowlist or denylist), and it does not tell you. zn hooks install claude-code and zn-gate status read the managed settings files on this computer and your own settings, and say when one of these stops zn's hooks or its secrets server. Ask your administrator to add zn to the managed settings. Policy sent through claude.ai, device management (MDM) or the Windows registry is not read, so when such a policy exists Claude Code may ignore the managed settings files. claude doctor and /status show which source Claude Code used.
4. Let a command use a secret
Allow the program that needs the value:
zn secret allow DATABASE_URL_PASSWORD --command psql
When the agent runs psql "postgres://app:__ZN_DATABASE_URL_PASSWORD__@db/app", the hook routes it through zn run. The value goes only into that process, and its output is masked: the raw, base64, hex and URL-encoded forms come back as the marker. The first time, the vault asks you to confirm in its dialog or its terminal, and the prompt shows the exact program that will run. Changing a secret's policy (zn secret allow) is confirmed the same way. The agent cannot answer these prompts from its own terminal.
Allow a specific program. node, python, npm, make and other interpreters run any code they are given, so allowing one lets every script it runs read the secret. zn warns you when you do.
5. Let the agent call an API
For HTTP APIs, use the secrets MCP server instead of putting the value in a process. zn hooks install prints how to register it for your agent. The agent calls secrets_http_request with the marker in a header:
{ "method": "GET", "url": "https://api.github.com/user",
"headers": { "Authorization": "Bearer __ZN_GITHUB_TOKEN__" } }
zn sends the request only to the secret's allowed hosts (allowHosts), only with the value in headers (to use it in the URL or the body, run zn secret allow NAME --in query|body), and returns the response with secrets replaced by markers. There is no tool that returns a value.
MCP configs
The secrets server above is the safe way for the agent to use a key. A key written in plain text in an agent configuration file is the opposite: it travels with the repository. zn audit reports a secret value in an agent config or instruction file (finding ZNA011) and never prints the value. See Audit.
Command reference
| Command | What it does |
|---|---|
zn unlock |
Create or unlock the vault; --foreground runs it in a terminal of its own |
zn lock |
Lock the vault now |
zn vault status |
Show the version of the running vault and whether it is outdated |
zn env shadow .env |
Move the values of a .env file into the vault and leave markers |
zn env unshadow |
Restore the original .env from the encrypted copy |
zn secret set NAME |
Store a secret from a hidden prompt; --host limits the hosts it may be sent to |
zn secret allow NAME --command PROG |
Let one program use the secret; --in query|body for URLs and bodies |
zn secret list |
Names, modes, hosts and commands (never values) |
zn secret rm NAME |
Remove a secret |
zn vault log |
Local audit log: who used which secret where, with no values. Also zn secret log; --since 1h limits it |
zn vault verify |
Check the audit log's HMAC chain |
zn hooks install <agent> |
Install the hooks for claude-code, codex, cursor or opencode |
zn hooks uninstall <agent> |
Remove the hooks. For Claude Code it also removes zn's own secrets MCP server, and keeps any other server with that name |
If the vault is locked, the hooks still redact what they can recognise, but with markers that cannot be restored (__ZN_REDACTED_AWS_KEY__), and they remind you to run zn unlock.
To check what is protected on your machine, run npx -y zn-gate check.