Skip to content

zn-gate 1.7.0

Choose a tag to compare

@github-actions github-actions released this 15 Oct 06:39
· 266 commits to main since this release

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 the analyze_prompt tool only; the text it checks is sent to api.usezn.com with secrets redacted first. Without a key the plugin stays local, and ZN_CLOUD, ZN_API_KEY, ZN_API_URL and ZN_STAGE from the environment are still ignored.
  • Claude Code plugin: a listing icon for the Claude plugin directory (icon.png at 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=off turns 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 stats sends one report per complete day with only the fields that zn-gate stats share show prints 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 on as an admin command an agent cannot run. Only built-in secret types are counted: a variable or secret name you chose is counted as OTHER (or ENV_VALUE for a value matched by its variable name), never by its name.
  • zn vault team says 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's secrets server is denied. Run zn hooks install cursor again to add this hook.
  • zn team join shows the organization name and who invited you in the vault's confirmation
    dialog, from POST /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 against api.usezn.com:
    another server given with --api gets 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 .key files 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 ::notice lines with --format github, and as skipped with total_skipped in JSON. Python's JSON total_files now 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_VERSION 2026-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_search or add_item are no longer blocked as rm or dd; format_disk, runShutdown and rm still 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[!]]v or .en[[:alpha:]] are recognised as naming .env.
  • zn audit no longer reports zn's own secrets MCP server as an unknown server when its command ends with the --agent <id> that zn hooks install adds.
  • zn init finds the Antigravity MCP settings: it checks ~/.gemini/antigravity/mcp_config.json and ~/.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 run keeps interpreter start-up variables such as NODE_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 stats command with many flags, which could stall the hook.
  • zn-gate scan --format github (Python) escapes file names and messages in its ::error and ::notice lines, 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-gate on 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, and Authorization: Bearer tokens. Generic assignments such as password = "..." 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 github prints 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