Repository navigation
zn-gate 1.12.0
zn-gate 1.12.0 adds zn-gate reach, a read-only report of what the coding agents on this machine can reach (secret names, credential files, MCP servers and agent settings) with the command that fixes each finding, also as SARIF for code scanning. Releases are now built and signed in a separate workflow (SLSA Build Level 3), and release tags are signed by a workflow with gitsign. zn audit stops showing secret values it did not recognise in the commands it quotes, and the agent hooks refuse long shell patches quickly again. The agent hooks also refuse more of the ways a shell command can reach a .env file with values (see Security). Update with npm install -g zn-gate@1.12.0 and pip install -U zn-gate.
If you verify releases: from 1.12.0 on, the signer workflow of the GitHub attestations and Sigstore signatures is build-release.yml, not release.yml; docs/verify-release.md has both and the tag check.
Added
zn-gate reach [path]shows, in one screen, what the coding agents on this machine can reach: which agents are found and whether zn protects them, the.envfiles of the folder with the names of their secrets, the credential files in your home folder (~/.aws/credentials, SSH keys,~/.npmrcand others: checked only for whether they exist, never opened), the names of environment variables that hold a secret, and whatzn auditfinds in the MCP servers and agent settings of the folder and of your user. Each finding has the command that fixes it. It is read only: no value is ever shown, nothing is sent, no hook is started, and nothing is written unless you ask for it with--remember.--jsongives the same report as JSON.zn-gate reach --sarifwrites the report as SARIF 2.1.0 for code scanning. Findings about a file of the checked folder are results: thezn auditfindings keep their rules, and a.envwith secrets an agent can read isZNR002. What belongs to the machine (agents not protected, credential files, environment variables with secrets) and files in the home folder are notifications of the run.--fail-on high|medium|lowexits 1 when a finding at or above that level is found; the default,none, keeps reach a report that exits 0. A usage error or a failure exits 2.zn-gate reach --rememberkeeps the counts of the run (high, medium, low and the date, nothing else) in~/.zn/reach-last.json, so the next run with--remembershows what they were last time. Without it, reach writes nothing.zn-gate checkandzn hooks install codexsay when Codex will not run zn's hooks: hooks turned off with[features] hooks = falsein your organization'srequirements.tomlormanaged_config.tomlor in your ownconfig.toml, orallow_managed_hooks_onlyinrequirements.tomlwithout zn among the managed hooks. Codex gives no sign of it. Claude Code'sdisableAllHooksandallowManagedHooksOnlywere already reported.
Changed
- The README and the npm package page show the getting-started path in four steps:
zn-gate reach,zn-gate init,zn-gate checkandzn-gate env shadow.zn-gate reachis documented in full indocs/reach.md: what it shows, its flags and exit codes, the memory file of--remember, SARIF upload to code scanning, and what it never sends. - The two MCP servers (
zn mcpand thesecretsserver) are now checked against Claude Code 2.1.302 and the newest MCP SDK client. They keep answering with MCP protocol version 2024-11-05, whatever version the client asks for, and the compatibility page says so.
Fixed
- The agent hooks: a long patch sent as a shell command (
apply_patchin Codex) is refused in a fraction of a second again. To name a secret file in the message, zn now checks only the first 32 files of the patch (and, in other refused commands, the next 32 words after a wildcard) instead of every one on disk, which took several seconds for a patch near the 100 KB limit and could reach the agent's time limit for the hook on a slow machine. The decision does not change.POLICY_RULES_VERSIONis2026-10-17.2. - The agent hooks on Windows: a PowerShell command that already runs through
zn-gate run -- ...with a secret placeholder is left as it is. Before,zn-gatewas taken for a PowerShell cmdlet because of its name and the command was refused (zn run -- ...worked).
Security
- GHSA-rccq-h3xc-j9qj: the agent hooks could let a protected file be read without being checked when the command reached it indirectly, or when the command was too long or slow to check. The bullets below are the fixes.
zn audithides a secret value given as the next item of a JSON array after a credential flag, as in the arguments of an MCP server:"args": ["--api-key", "..."]. Earlier versions could quote it in the evidence of a finding.zn auditno longer shows secret values in a format it does not know when they follow a credential flag (--token,--api-key,--password), an auth header (Authorization: Bearer ...,x-api-key: ...) or a name such asAPI_TOKEN=, in the command lines and hook commands it quotes. Earlier versions could quote such a value in a finding; rotate any that a finding showed. References such as$TOKENor${API_KEY}are kept.- Releases are built, attested and signed by a separate reusable workflow,
build-release.yml, whose build and signing jobs the release workflow calls but cannot reach. That makes the build provenance of the npm and Python packages SLSA Build Level 3. To verify a release from 1.12.0 on, the signer workflow of the GitHub attestations and Sigstore signatures isusezn/zn/.github/workflows/build-release.yml(earlier releases:release.yml; npm provenance and PyPI attestations still namerelease.yml); seedocs/verify-release.md. The agent plugin's checks pick the signer workflow from the release's own tag. - Release tags are created and signed by a workflow,
tag-release.yml, with gitsign (Sigstore keyless signing), so the signature names that workflow and no person's identity. It runs only frommain, checks that the commit is the head of its release branch, that every version matches and that every check passed, and waits for approval before it signs. From 1.12.0 on, the release workflow refuses to publish a tag unless it carries a valid signature fromtag-release.yml. How to check a tag:docs/verify-release.md. - The agent hooks: a
.envfile with values is refused when a command reaches it after changing directory (cd app && cat .env, also when the current folder's.envis shadowed), through a path built while the command runs (cat $DIR/.env,cat $(...)/.env), or from code given to an interpreter or shell (python -c "open('app/.env')",bash -c "cat app/.env"). Earlier versions only checked the path as typed, from the current folder.POLICY_RULES_VERSIONis2026-10-17.5. - The agent hooks: a
.envfile with values is refused when a shell command names it with bash$'...'quoting (cat $'\x2eenv', also with a NUL that ends the string) or$"...", through an input redirection with a descriptor number or in read-write mode (cat 0<.env,cat <>.env,exec {fd}<.env), or as the value of aname=valueargument (dd if=.env).POLICY_RULES_VERSIONis2026-10-17.6. - The agent hooks: reading a
.envor credential file from the git repository (git show HEAD:.env,git cat-file -p :app/.env) or its history (git log -p -- .env,git checkout HEAD -- .env,git archive HEAD .env) is refused, like reading it from disk, also when the file is no longer on disk. Clean-up steps still work:git rm --cached .env,git restore --staged .env,git checkout -- .env,git status,git check-ignore .envandgit log --oneline -- .env.POLICY_RULES_VERSIONis2026-10-17.7. - The agent hooks: a shell command that names a
.envfile in a subfolder through a wildcard (cat app*/.env,cat src/*/.env,cat {a,b}/.env) is refused, like the plain path. Earlier versions let it run, because the file was looked for under the pattern itself and not found.POLICY_RULES_VERSIONis2026-10-17.3. - Rust gateway in
src/(experimental, not distributed: not part of the npm or PyPI packages or the plugin): post-quantum policy signatures are ML-DSA-65 (FIPS 204), verified with the maintained RustCryptoml-dsacrate, instead of Dilithium3 from the unmaintained PQClean bindings; threecargo auditexceptions are gone. A policy signed with a Dilithium3 key must be signed again with an ML-DSA-65 key. Seedocs/rust-core.md. - The agent hooks: a private key kept outside
~/.ssh, for example in a project, is refused like one in~/.ssh:id_rsa,id_ed25519and the other SSH key names, key stores (.p12,.pfx,.jks,.ppk), and.pem,.keyor.p8files that hold a private key. Certificates and public keys stay readable.POLICY_RULES_VERSIONis2026-10-17.8. - The agent hooks look inside a
.envor key file without blocking: a FIFO or other special file with such a name is refused without being read, and a name repeated in a long command is checked once. - The agent hooks check a shell command within a time and disk budget, well below the agent's hook timeout: a command with too many paths to check in time is refused, instead of running unchecked once the agent stops waiting.
- The agent hooks check every command in a long chain. Earlier versions checked only the first 1000 simple commands (lines count too) and let the rest run unchecked.
How to verify these files: docs/verify-release.md