Repository navigation
zn-gate 1.6.0
If you use zn-gate 1.5.x, update. In 1.5.x, zn-gate scan (Node.js) printed PASSED for a folder whose .env or other files held API keys, because it only reported prompt-injection text. 1.6.0 reports exposed credentials too (see 1.6.0-beta.1 below). After updating, run zn-gate scan again on your projects. This release also includes everything in 1.6.0-beta.1.
Added
- Every rule layer has a version:
SCRUB_RULES_VERSION(secret and PII patterns),AUDIT_RULES_VERSION(zn auditrules) andPOLICY_RULES_VERSION(hook policy).SCRUB_RULES_VERSIONis2026-10-14.2,POLICY_RULES_VERSIONis2026-10-14.4, andAUDIT_RULES_VERSIONis2026-10-14.1.zn-gate --versionandzn-gate statusshow the scrub and audit versions next to the rules engine version.
Changed
- Block and error messages now say what happened and what to do next, in plain words. The CLI prints the code (such as
ZN_LOCKED) only withZN_VERBOSE=1orDEBUGset, and the suggested commands (zn unlock,zn secret set NAME,zn env shadow FILE) fall back tonpx -y zn-gate@<version>whenznis not on your PATH. - Default policy by tiers. zn-gate still blocks what is almost certainly an attack or a leak: credential files, requests for secrets, exfiltration links, Base64 or invisible-character evasion, "ignore previous instructions", the narrow forms of the rules above and dangerous commands. Ten heuristic rules that also match ordinary text (
pi:identity_override,pi:system_tag,pi:system_bracket,pi:new_instructions,pi:pretend,pi:roleplay,pi:jailbreak,pi:bypass,pi:override,pi:template_boundary) now give an advisory by default instead of a block. Strict mode blocks them as before:ZN_STRICT=1,"strict": trueinzn.config.json, astrictline in.znrules, thestrictoption in Node.js orevaluate(text, strict=True)in Python. In our tests, benign texts blocked on a public prompt-injection dataset went from 8 of 7,150 to 0; injections blocked went from 24.0% to 22.4% and from 27.7% to 21.3% on two public datasets, and every injection that is no longer blocked gets an advisory.zn-gate testcounts an advisory on an attack sample as detected and shows it as ADVISORY.zn auditandzn-gate scankeep reporting these patterns (they use strict mode). The Pythoncheck_tool_call,check_tool_resultandsanitize_tool_resultreturn the advisory and takestrict=True. Strict mode set in the environment or a config file cannot be turned off by passingstrict: false.RULES_VERSIONis now2026-10-14.3. - Fewer false blocks from the local rules. Four rules blocked a lot of ordinary text, such as documentation, code and requests like "act as a gateway". They now block only the clear form, and the broader match is reported as an advisory: the verdict stays
allowand the result carriesadvisorywith the rule and a short reason (zn-gate analyze,analyze_prompt, Pythonevaluate()andanalyze()).exfil:credentialsblocks when a verb such as "send", "print" or "read" starts an instruction and a credential word ("API key", "password", "token",.env) follows in the same sentence, within 80 characters. Before, the two words could be anywhere in the text, so whole documents were blocked.indirect:hidden_tagblocks when the instruction-like word is inside the hidden element. Before, any later word such as "system" counted.pi:act_asblocks a request to the model to act as an assistant, a model, a persona or an unrestricted mode ("You will act as …"), not "act as a gateway".pi:forgetno longer blocks "forget all cached entries", "forget your password" and similar phrases about data.
In our tests on 747 public files (package READMEs and code, docs), files blocked by the local rules went from 5.6% to 1.1%; on two public prompt-injection datasets, benign texts blocked went from 2.1% to 0.1%, injections blocked went from 28.1% to 24.0% and from 30.9% to 27.7%, and every injection that is no longer blocked gets an advisory. The local rules are a first layer; the cloud check (ZN_CLOUD=1) is unchanged.RULES_VERSIONis now2026-10-14.2.
- CI fails when the local rules block more benign text. A new gate runs the rules engine over the pinned benign corpus and over a list of ordinary developer sentences, and compares the block counts per rule with a committed baseline.
- CI also fails when a rule layer changes without a higher version, or when its rule ids change without an updated snapshot. When the block counts fall, the gate passes and warns that its baseline is stale.
Fixed
zn unlockno longer hangs at "Repeat passphrase" when the second line arrives before its prompt (for example when both lines are pasted at once). Each prompt used its own line reader and the second line was lost, so the vault was never created andzn env shadowthen reported that the vault was locked. Wait for each prompt before typing: text typed before a prompt appears can be shown on screen by the terminal. Different passphrases, Ctrl+C and Ctrl+D now end with a clear message instead of waiting.
Security
- Agent hooks: an AI agent could run the zn commands that manage your secrets and protection (
zn unlock,zn lock,zn secret ...,zn env shadow|unshadow,zn hooks install|uninstall) by calling a specific package version, for examplenpx -y zn-gate@1.5.1 hooks uninstallornpm exec zn-gate@1.5.1 -- unlock: the hook only recognised the plainznandzn-gatenames. Most of these commands also refuse to run without an interactive terminal or when they detect a coding agent, which stopped the usual agents;zn lockhad no such check. The hooks now deny the versioned forms withnpx,npm exec,pnpm dlx,yarn dlxandbunx. Affected: 1.5.0-beta.1 to 1.6.0-beta.1. - Trigger words are now found in the same way in Node.js and Python. A non-ASCII letter next to a trigger word (for example
éjailbreak) no longer hides it: Python previously allowed this while Node.js blocked it, and both now block. - Compatibility characters are folded before matching: fullwidth Latin text such as
ignoreis treated asignore, in both languages. The folding is used only for detection; the text returned to you is not changed. - Invisible characters (zero-width spaces and joiners, the word joiner, variation selectors and Unicode tag characters) are ignored when matching, so a trigger word or a secret split by them is still detected.
- The dotless i (
ı) and the capital dotted I (İ) are read asiandIwhen matching, so trigger words spelled with them are detected in both languages. - A secret that contains invisible characters is redacted as a whole, including those characters. Before, a key such as
AKIAfollowed by a zero-width space was not redacted. - The Node.js scrubber finds such secrets too and reports their full span.
- The PII & secret scrubber now recognizes more credential formats: Docker Hub personal and organization access tokens (
dckr_pat_,dckr_oat_), Tavily keys (tvly-), Firecrawl keys (fc-), Polar tokens (polar_oat_,polar_pat_,polar_at_u_,polar_at_o_,polar_rt_u_,polar_rt_o_,polar_ci_,polar_cs_), Replicate tokens (r8_, 40 characters), Supabase personal access tokens (sbp_) and Azure AD client secrets. - Coverage fixes: Hugging Face tokens made only of letters, Stripe
sk_prod_keys, Twilio API key SIDs in upper case, and legacy 32-character hexadecimalsk-keys are now detected by the scrubber as well.
How to verify these files: docs/verify-release.md
Security advisory
This release fixes GHSA-wvv2-f3m6-6wrq (moderate): an agent could run zn admin commands through a versioned package call such as npx -y zn-gate@<version> hooks uninstall, skipping the hook that denies them. Update to 1.6.0.