Local security layer for coding agents: prompt-injection checks, secret redaction, MCP proxy.
Runs on your machine from its package; the steps are below.

Not assessed yet.
Odel charges nothing per call. The publisher may charge for its own service.
Runs locally over stdio
This server isn't hosted — your MCP client launches it from a package registry. Use one of the commands below, or drop the config into your client (e.g. Claude Desktop).
Run
npx -y zn-gate
MCP client config
{
"mcpServers": {
"zn-gate": {
"command": "npx",
"args": [
"-y",
"zn-gate"
]
}
}
}
Use AI coding agents without exposing your secrets, your data or your systems.
An open-source security layer that runs on your machine, between your agent and the tools and content it touches.
Docs · npm package · Python package · Security · Discord
For: developers using Claude Code, Codex CLI, Cursor, OpenCode or any MCP client, and teams building agents with MCP, LangChain, CrewAI or LlamaIndex.
What it does: keeps secret values out of what your agent sends to its model, checks what your agent reads and calls for prompt injection, and audits prompts, skills and configs. Everything runs on your machine, is MIT-licensed and needs no account. The Python package has no runtime dependencies; the npm package's only one is age-encryption (with its @noble/@scure dependencies) for the secret vault, bundled in the package.
Keep secrets out of your agent's context. The secrets scrubber and broker for coding agents keeps real values in an encrypted vault on your machine, and your agent works with markers such as __ZN_STRIPE_KEY__.
npm install -g zn-gate # Node.js 18+
zn unlock # create or unlock the local vault
zn env shadow .env # move .env values into the vault, leave markers
zn hooks install claude-code # or: codex, cursor, opencode
Windows PowerShell:
npmandnpxcan fail with "running scripts is disabled on this system", because the default execution policy blocksnpm.ps1. Usenpm.cmdandnpx.cmdinstead (for examplenpm.cmd install -g zn-gate), run the commands from Command Prompt, or allow local scripts for your account withSet-ExecutionPolicy -Scope CurrentUser RemoteSigned.
Cursor support is partial: zn-gate can only rewrite the output of MCP tools there, not of shell commands or file reads.
Five-minute walkthrough: docs/secrets.md. What each agent supports: docs/compat.md. Limits: docs/threat-model.md.
Add zn-gate as an MCP server. Pick your agent. Each command adds zn-gate as an MCP server (Node.js 18+).
Claude Code
claude mcp add zn-gate -- npx -y zn-gate mcp
Codex CLI
codex mcp add zn-gate -- npx -y zn-gate mcp
Cursor: add to ~/.cursor/mcp.json
{ "mcpServers": { "zn-gate": { "command": "npx", "args": ["-y", "zn-gate", "mcp"] } } }
OpenCode: add to ~/.config/opencode/opencode.json
{ "mcp": { "zn-gate": { "type": "local", "command": ["npx", "-y", "zn-gate", "mcp"], "enabled": true } } }
Protect the MCP servers you already use. init finds them in your agent configs, writes a backup, and wraps each local server with the zn-gate proxy:
npx -y zn-gate init --dry-run # preview
npx -y zn-gate init # wrap (backup first)
npx -y zn-gate init --revert # undo
It reads Claude Desktop, Claude Code (~/.claude.json), Cursor (~/.cursor/mcp.json), Codex CLI (~/.codex/config.toml) and OpenCode (~/.config/opencode/opencode.json). Full list and rules.
Python (rules engine and secret redaction; the vault, hooks and zn run are in the npm package)
pip install zn-gate
Send a tool output with a fake AWS key (AWS's documentation example) and a database password through zn-gate, exactly as your agent would. Requires zn-gate 1.4.1 or later.
printf '%s\n' '{"jsonrpc":"2.0","id":1,"method":"tools/call","params":{"name":"check_tool_result","arguments":{"tool_name":"read_file","content":"AWS_ACCESS_KEY_ID=AKIAIOSFODNN7EXAMPLE\nDATABASE_URL=postgres://app:hunter2@db:5432/main"}}}' \
| npx -y zn-gate@latest mcp | jq -r '.result.content[0].text'
jq only pretty-prints; without it, look for sanitized_content in the raw JSON. That field is what the model would see:
"secrets_redacted": 2,
"sanitized_content": "AWS_ACCESS_KEY_ID=[REDACTED_AWS_KEY]\nDATABASE_URL=postgres://app:[REDACTED_DB_PASSWORD]@db:5432/main"
Nothing was sent over the network. Python equivalent:
python -c "from zn_gate import sanitize_tool_result as s; print(s('read_file', 'AWS_ACCESS_KEY_ID=AKIAIOSFODNN7EXAMPLE')['sanitized_content'])"
Then run the local self-test (51 curated vectors: 26 attacks, 25 benign):
npx -y zn-gate test
Where zn-gate sits in a tool call, as an MCP server or as the shield proxy around other servers:
sequenceDiagram
participant A as Agent
participant Z as zn-gate (on your machine)
participant T as Tool or MCP server
A->>Z: analyze_prompt
Z-->>A: allow, or block with the rule that fired
A->>Z: check_tool_call (name, arguments)
Z-->>A: allow with secrets masked, or block
A->>T: run the tool
T-->>Z: output (check_tool_result, or through shield)
Z-->>A: output with secrets redacted, or neutralized if it carries an injection
check_tool_result, tool arguments checked with check_tool_call, and output of MCP servers wrapped by zn-gate shield / init. Python: sanitize_tool_result, @guard(mask_secrets=True) and the framework integrations.__ZN_AWS_KEY_1__); zn run fills values only into commands you approved; personal data (cards, IBAN, national IDs, e-mail, phone) is detected too. What each agent allows differs: see docs/compat.md.analyze_prompt, only if you set ZN_CLOUD=1 and ZN_API_KEY. The secrets zn-gate detects are removed from the prompt on your machine before it is sent; personal data is not removed on this path.zn-gate scan <path> audits prompt templates, AGENTS.md / SKILL.md files and configs for injection traps and leaked secrets (exit code 1 on findings, for CI). The same check runs as a GitHub Action. MCP servers can be checked on the web at usezn.com/scan.Everything above runs locally, for free, with no account. The optional cloud at usezn.com is for teams that need a shared record of what their agents did:
ZN_CLOUD=1).By default nothing is sent. If you set ZN_CLOUD=1 and an API key, analyze sends the prompt text after removing the secrets zn-gate detects locally: personal data is not removed on this path, and a secret it does not detect would be sent with the text. The dashboard keeps the metadata of each cloud decision. Tool output and tool-call arguments are never sent.
Nothing, by default.
flowchart LR
subgraph local["Your machine"]
A["Agent"]
Z["zn-gate<br/>rules engine + secret redaction"]
T["Tools and MCP servers<br/>files, shell, web, databases"]
end
C["api.usezn.com<br/>Galvanize 2 classifier"]
A -- "tool calls and prompts" --> Z
Z -- "allowed calls, secrets masked" --> T
T -- "tool output" --> Z
Z -- "redacted output, verdicts" --> A
Z -. "prompts only, detected secrets removed,<br/>only with ZN_CLOUD=1 and ZN_API_KEY" .-> C
| Data | Evaluated | Sent anywhere? |
|---|---|---|
Tool output (check_tool_result, shield) | Locally | Never, in any configuration |
Tool-call arguments (check_tool_call, shield) | Locally | Never, in any configuration |
MCP tool and parameter descriptions (shield) | Locally | Never, in any configuration |
Prompts (analyze_prompt, zn-gate analyze, Python analyze()) | Locally | Only if you set both ZN_CLOUD=1 and ZN_API_KEY. Detected secrets are removed locally before sending; personal data is not. |
ZN_API_KEY alone enables nothing. ZN_LOCAL_ONLY=true overrides ZN_CLOUD=1. npx -y zn-gate status prints what your configuration sends.
1.4.0 and earlier: with
ZN_API_KEYset, the Node package sent tool output and tool-call arguments toapi.usezn.comwithout redaction. 1.4.1 removes that path. Details in the CHANGELOG.
We only publish numbers we can reproduce, with the method next to them.
bench/bench_by_size.js), in the npm package README. Short summary from that run: about 0.01 ms for a 200 B prompt, 0.3 ms for a 4 KB tool output with redaction, about 4 ms for 64 KB with redaction (p50).zn-gate scan --code <path>; for the MCP servers you wrap, --shadow logs detections without blocking.OPENAI_API_KEY=) is partial in this release.| Variable | Default | Effect |
|---|---|---|
ZN_CLOUD | off | 1 lets prompt analysis also query the cloud classifier. Requires ZN_API_KEY. |
ZN_API_KEY | unset | API key from usezn.com/dashboard. No effect without ZN_CLOUD=1. |
ZN_LOCAL_ONLY | unset | true forces local-only, even with ZN_CLOUD=1. |
ZN_STAGE / ZN_API_URL | v30 | Cloud endpoint selection. |
zn Team: zn team join <token> connects a machine to your organization's signed secrets policy and uploads its audit log (metadata only). What the organization can and cannot change on the machine, offline behavior and the OpenTelemetry export (zn vault log export --otlp): docs/team.md.
Custom rules: add banned patterns and forbidden paths in .znrules or zn.config.json at your project root (example).
Audit log: zn-gate shield appends each decision to ~/.zn/evidence.jsonl as a hash-chained (SHA-256) record with a redacted preview. It detects accidental or partial edits; it is not tamper-proof against someone with write access to the file. Check it with npx -y zn-gate evidence --verify.
The action installs the published zn-gate package from PyPI at an exact version and runs zn-gate scan on the path you give it. The scan runs entirely on the runner and sends nothing anywhere.
name: AI agent security scan
on:
pull_request:
paths:
- 'prompts/**'
- '**/*.prompt'
- '**/AGENTS.md'
- '**/SKILL.md'
permissions:
contents: read
jobs:
zn-gate-scan:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4.4.0
with:
persist-credentials: false
- name: Scan AI agent prompts and tools
# Pin the action to a full commit SHA; the comment names the release.
# v1.4.1 does not have the package-sha256 input (v1.5.0 and later).
uses: usezn/zn@d9da1b1a87e12c4da468cb9beec425dc3f972c5f # v1.4.1
with:
version: '1.5.0'
path: '.'
fail-on-threat: 'true'
format: 'github'
| Input | Default | Description |
|---|---|---|
version | current stable release | Exact zn-gate version to install from PyPI. Ranges and latest are refused. |
package-sha256 | empty | Requires v1.5.0 or later of the action. SHA-256 of the wheel for version, as listed on PyPI. When set, the install fails if the downloaded file has a different hash. Older releases of the action ignore this input with only a warning, so no hash is checked. |
path | . | File or directory to scan. |
fail-on-threat | true | Fail the step when a threat is found. Set false to report only. |
format | github | github (inline annotations), text or json. |
| Output | Description |
|---|---|
scan-exit-code | Exit code of the scan: 0 clean (or fail-on-threat: 'false'), 1 threats found, 2 error. Empty if the scan did not run. |
The action needs only contents: read. It installs the wheel with --only-binary=:all: --no-deps (zn-gate has no dependencies) into a private virtualenv, so no build step runs and your job's own Python packages are not touched. pip still reads the runner's own pip settings (PIP_* environment variables and pip.conf), so a mirror or proxy you configure there is used; set package-sha256 so that the wheel is checked whatever index it comes from. The action runs on pull_request, never on pull_request_target: do not check out untrusted pull-request code in a pull_request_target workflow.
packages/zn-gate: Node.js CLI, MCP server and shield proxy (npm).packages/zn-gate-py: Python SDK and CLI (PyPI).src/: legacy Rust gateway, not released (docs/rust-core.md).bench/: benchmark scripts and results.assets/brand/: logo files (SVG master and light/dark variants, PNG lockup and banners).Do I need an account or API key? No. Everything local works without one. An account is only needed for the optional cloud (team audit and the larger detection model).
Does zn-gate slow my agent down? The local check adds under a millisecond for typical prompts and tool outputs (p99 0.8 ms at 4 KB on our test host). See the measured table above.
Will it block legitimate content? Sometimes. Use init --shadow or shield --shadow to log detections without blocking while you evaluate it.
Which agents are supported? Any MCP client. init can rewrite configs for the agents listed under Install.
How do I remove it? npx -y zn-gate init --revert restores your configs from the backup. For a standalone server, remove the zn-gate entry from your agent config.
Report vulnerabilities to security@usezn.com. Policy, scope and safe harbor: SECURITY.md. Hall of Fame: usezn.com/security.
Releases published by the release workflow, starting with the first one after 1.5.0-beta.1, are signed and carry build provenance. 1.5.0-beta.1 and earlier versions were published by hand and have no signatures or provenance. To check a download: docs/verify-release.md.
Issues and pull requests are welcome. See CONTRIBUTING.md.
MIT. Copyright (c) 2026 usezn (usezn.com). The Galvanize 2 and Galvanize-60M weights are Apache-2.0. The zn-prompt-injection-bench dataset is CC-BY-4.0. The npm package bundles third-party code listed in THIRD_PARTY_NOTICES.md.