Repository navigation
zn-gate 1.7.0
zn-gate 1.7.0 adds zn-gate stats (local counts of what zn-gate did), MCP argument checks in Cursor, a preview of the organization before zn team join, and an optional cloud classifier key for the Claude Code plugin. The Python package redacts the same provider tokens as Node.js, zn-gate scan lists the files it does not read, the Rust engine's rules are more precise, and the agent plugin launcher only loads verified files.
What can leave your machine, all off by default: with zn-gate stats share on, one pseudonymous report of counts a day (a random installation id, never values, prompts, names or paths); with the Claude Code plugin's API key, the text analyze_prompt checks, with secrets redacted, to api.usezn.com; and zn team join sends the join token to api.usezn.com for the preview before the confirmation dialog.
Added
- Claude Code plugin: an optional API key setting (
api_key, stored as a sensitive value) turns on the zn cloud classifier for theanalyze_prompttool only; the text it checks is sent to api.usezn.com with secrets redacted first. Without a key the plugin stays local, andZN_CLOUD,ZN_API_KEY,ZN_API_URLandZN_STAGEfrom the environment are still ignored. - Claude Code plugin: a listing icon for the Claude plugin directory (
icon.pngat the plugin root, named in the manifest), and the plugin's contact email is now support@usezn.com (security reports still go to security@usezn.com). zn-gate stats(Node and Python) shows what zn-gate did on this machine: checks, blocks, secret values replaced before the model saw them, and the secret types found, per agent, for the last 30 days (--days N,--json). The agent hooks, the MCP tools and the shield proxy keep these counts in~/.zn/stats.json(numbers only; never values, prompts, paths or hosts).ZN_STATS=offturns counting off. Nothing is sent: sharing pseudonymous daily counts is a separate opt-in (zn-gate stats share on,off,status,show), off by default; when on,zn-gate statssends one report per complete day with only the fields thatzn-gate stats share showprints first.- Sharing can only be turned on by you, in your own terminal, after a yes/no question: the agent hooks treat
zn-gate stats share onas an admin command an agent cannot run. Only built-in secret types are counted: a variable or secret name you chose is counted asOTHER(orENV_VALUEfor a value matched by its variable name), never by its name. zn vault teamsays that a shared vault for teams is planned and counts the request on this machine only. Nothing is sent and no contact details are asked for.- Cursor checks the arguments of MCP tool calls: a call that would send a secret value, or a
__ZN_marker, to an MCP server other than zn'ssecretsserver is denied. Runzn hooks install cursoragain to add this hook. zn team joinshows the organization name and who invited you in the vault's confirmation
dialog, fromPOST /v1/org/join-preview, before this machine sends its audit key. The preview
does not use up the join token; an invalid, expired or used-up token, or an organization without
an active subscription, stops before the dialog. The check is made only againstapi.usezn.com:
another server given with--apigets the token only after you confirm, as before. If the
organization joined is not the one shown, the join is refused and nothing is saved. Names from
the server are shown without control, zero-width or bidirectional characters.
Changed
zn-gate scan(Node.js and Python) reports the files it does not read: files of 2 MiB or more, binary.keyfiles and files that cannot be read (Python also lists pipes and other files that are not regular files, which it no longer opens). Node prints them after the summary; Python lists them in text output, as::noticelines with--format github, and asskippedwithtotal_skippedin JSON. Python's JSONtotal_filesnow counts only the files that were read.- Claude Code plugin: the manifest's homepage and documentation links point to usezn.com instead of a folder of this repository.
- The Rust engine's prompt-injection rules return a stable rule ID (for example
pi:ignore_previous; the block message reads "Prompt Injection blocked: pi:") and use a grammar based on the Node.js rules for "ignore / disregard / forget ... instructions", so phrasings such as "ignore the instructions above" or "ignore these instructions" are caught while "you can ignore the build instructions" is not. Phrases that are common in ordinary text are no longer blocked: "act as a", "respond as a", "debug mode", "root access", "shell access", "user message:", "end of conversation", "end of transmission", "stay in character", "prompt injection", "stop what you are doing", "without any restrictions", "imagine you are a", "into developer mode", "switch to developer mode" and "override system" (now only "override the system prompt" and similar). "system message:" and "new instructions:" count only at the start of a line, "forget everything" only at the end of a clause or followed by "above", "before" or "you know/were told", "do anything now" only as "you can do anything now" or with "DAN", and "jailbreak" must be a whole word. - The hook policy rules are at
POLICY_RULES_VERSION2026-10-14.10(see the entries below that change them).
Fixed
- The Rust engine's restricted-commands rule now matches whole words in tool names too, so tools such as
transform_data,perform_searchoradd_itemare no longer blocked asrmordd;format_disk,runShutdownandrmstill are. - The Rust engine's log scrubber now redacts card numbers only when they pass the Luhn check and match a card network, US SSNs only with a valid area, group and serial, and phone numbers only in E.164 form or as a formatted North American number, never a digit run inside a longer number. IDs and timestamps are no longer redacted as phone numbers.
- The agent hooks read glob bracket expressions as the shell does. A command with one that is not a valid regular expression (for example
[z-a]or[a\]) no longer makes the hook fail, and a]as the first member or a POSIX class such as[[:alpha:]]is now understood, so words like.e[!]]vor.en[[:alpha:]]are recognised as naming.env. zn auditno longer reports zn's ownsecretsMCP server as an unknown server when its command ends with the--agent <id>thatzn hooks installadds.zn initfinds the Antigravity MCP settings: it checks~/.gemini/antigravity/mcp_config.jsonand~/.gemini/config/mcp_config.json(each one only if it exists). It looked for~/.gemini/antigravity/mcp.json, which Antigravity does not use, so Antigravity was never found.- The threat model said that
zn runkeeps interpreter start-up variables such asNODE_OPTIONS; since 1.5.1 it removes them whenever it injects secret values.
Security
- The agent hooks' check for zn admin commands no longer takes exponential time on a crafted
zn statscommand with many flags, which could stall the hook. zn-gate scan --format github(Python) escapes file names and messages in its::errorand::noticelines, so a file name cannot add a line or a workflow command of its own to the CI log.- Agent plugin launcher: JavaScript modules that Node loads by itself (zn-gate's encryption libraries) can only come from the verified zn-gate files, and are run from the bytes that were checked. This covers ES modules, CommonJS and JSON that Node loads, and the require() calls inside them. It needs Node.js 22.15 or 23.5 or later for the hooks; the MCP servers have it on every supported version (before Node.js 22.15 without createRequire(), which the release build refuses). Before, a module missing from the package could be looked up in node_modules folders outside it, and the source was read from disk again after the check.
- The Python package (
zn-gateon PyPI) now redacts the same provider token formats as the Node.js scrubber: Stripe, Slack, GitLab, npm, PyPI, SendGrid, Google, Hugging Face, Twilio, AWS STS, Azure storage and AD secrets, DigitalOcean, Shopify, Mailgun, Groq, Replicate, xAI, Docker Hub, Tavily, Firecrawl, Polar, Supabase and other provider tokens, passwords in URLs, andAuthorization: Bearertokens. Generic assignments such aspassword = "..."in source files are not yet redacted by the Python package. Token-like runs of 24 or more characters that remain after redaction are masked in printed output and in the evidence ledger preview. zn-gate scan --format githubprints the rule, the reason and the file and line only. It no longer prints the matched line, so CI logs do not show credential text.- The evidence ledger directory is created with mode 0700 and the ledger file with mode 0600 on POSIX systems, in both the Node.js and the Python packages. A ledger that others can read is restricted on the next write.
How to verify these files: docs/verify-release.md