Skip to content

zn-gate 1.12.0

Choose a tag to compare

@github-actions github-actions released this 18 Oct 10:50
· 246 commits to main since this release

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 .env files of the folder with the names of their secrets, the credential files in your home folder (~/.aws/credentials, SSH keys, ~/.npmrc and others: checked only for whether they exist, never opened), the names of environment variables that hold a secret, and what zn audit finds 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. --json gives the same report as JSON.
  • zn-gate reach --sarif writes the report as SARIF 2.1.0 for code scanning. Findings about a file of the checked folder are results: the zn audit findings keep their rules, and a .env with secrets an agent can read is ZNR002. 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|low exits 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 --remember keeps the counts of the run (high, medium, low and the date, nothing else) in ~/.zn/reach-last.json, so the next run with --remember shows what they were last time. Without it, reach writes nothing.
  • zn-gate check and zn hooks install codex say when Codex will not run zn's hooks: hooks turned off with [features] hooks = false in your organization's requirements.toml or managed_config.toml or in your own config.toml, or allow_managed_hooks_only in requirements.toml without zn among the managed hooks. Codex gives no sign of it. Claude Code's disableAllHooks and allowManagedHooksOnly were 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 check and zn-gate env shadow. zn-gate reach is documented in full in docs/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 mcp and the secrets server) 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_patch in 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_VERSION is 2026-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-gate was 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 audit hides 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 audit no 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 as API_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 $TOKEN or ${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 is usezn/zn/.github/workflows/build-release.yml (earlier releases: release.yml; npm provenance and PyPI attestations still name release.yml); see docs/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 from main, 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 from tag-release.yml. How to check a tag: docs/verify-release.md.
  • The agent hooks: a .env file with values is refused when a command reaches it after changing directory (cd app && cat .env, also when the current folder's .env is 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_VERSION is 2026-10-17.5.
  • The agent hooks: a .env file 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 a name=value argument (dd if=.env). POLICY_RULES_VERSION is 2026-10-17.6.
  • The agent hooks: reading a .env or 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 .env and git log --oneline -- .env. POLICY_RULES_VERSION is 2026-10-17.7.
  • The agent hooks: a shell command that names a .env file 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_VERSION is 2026-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 RustCrypto ml-dsa crate, instead of Dilithium3 from the unmaintained PQClean bindings; three cargo audit exceptions are gone. A policy signed with a Dilithium3 key must be signed again with an ML-DSA-65 key. See docs/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_ed25519 and the other SSH key names, key stores (.p12, .pfx, .jks, .ppk), and .pem, .key or .p8 files that hold a private key. Certificates and public keys stay readable. POLICY_RULES_VERSION is 2026-10-17.8.
  • The agent hooks look inside a .env or 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