Repository navigation
Conversation
…ode9Firewall#413 part 1) The two git rules were regexes anchored at the start of the raw text, so a destructive call written indirectly passed (`cmd="reset --hard"; git $cmd`, `git config alias.x 'reset --hard' && git x`, `git -C dir reset --hard`, `env … git`, `timeout 30 git`, `sh -c 'git push -f'`) and four ordinary commands were stopped (`push --dry-run --force`, `clean -fn`, `branch -d`, a plain `rebase`). shell/git.ts reads every git invocation from the shell AST in the scope the credential jail already walks: variables assigned earlier in the command, `$(echo …)`, `for` loops over literal items, wrappers, `sh -c` / `eval` payloads and a heredoc into a shell, functions at their calls with "$@" bound, git's global options, and aliases defined in the same command (`git config alias.*`, `git config set`, `-c alias.*`, `!` aliases). An alias definition is judged by its value; a subcommand that cannot be read is a review. Force push also covers --force-if-includes, --mirror and +refspec. Short options bundle as git reads them (`-fn`, `-df`). Wiring: the AST review is carried to the tier-3 candidates so a user `block-force-push` rule still wins; a `block` on the check returns at once. The regex twins join AST_FS_REGEX_RULES and keep speaking for a command the parser refuses and (in the scan) for a user rule by the same name. The scan emits the same rule names; canonical-v32. Measured on this machine's own audit.log (15,483 distinct agent git commands): old flagged 100, new 89; 55 lose the flag (rebases, branch -d, git text inside commit messages and heredocs), 44 gain it (reset --hard, branch -D, force pushes on their own line that the start-anchored regex never saw). 197 corpus rows written from the issue's categories, 8 integration rows through dist/cli.js. Design: node9/docs roadmap/active/git-and-agent-files-design.md. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…oks (node9Firewall#413 part 2) A new check, Soft log, Standard review, Strict block, for the agent switching off the git hooks that check its own work (OWASP ASI10): `commit --no-verify` / `-n` (bundles too), `--no-verify` on push, merge, rebase, am, cherry-pick and revert; `-c core.hooksPath=…`, `git config [scope] core.hooksPath …` written, set, added or unset, GIT_CONFIG_PARAMETERS or GIT_CONFIG_KEY_n naming it; HUSKY=0, HUSKY_SKIP_HOOKS, LEFTHOOK=0 or SKIP=… applied to the git call as a prefix, through env, or exported earlier in the command (HUSKY=0 npm ci is not a bypass); chmod -x / 644 on a hook; and a hook file under .git/hooks or .husky removed, moved away, truncated or written with text that runs nothing, through the shell or a file tool. An append and a hook written with real content are not. The shell half rides the part-1 walk in shell/git.ts, so every indirect spelling counts; the file half (files/git-hooks.ts) reads the accesses the collectors already produce, which gain an `append` flag for `>>` and `tee -a` (files/paths.ts is an extractor source, hence the hash bump; the version bump of part 1 already covers the re-scan). The policy's git tier loops over the two check ids, each through its own value; the file verdict is combined with the protected-file one by strictness inside evaluatePolicy. A developer running --no-verify from the terminal is not judged (the AST tier runs for agent calls only). 94 corpus rows, 3 level rows, 5 integration rows through dist/cli.js. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…hing at once (node9Firewall#413 part 3) A preference, not harm, so off at every level and set only by a person (node9 checks set, the config `checks` map, the dashboard). Once on, the walk of part 1 reports `git add -A` / `--all` / `-u` with no path, `git add .` and any pathspec that is the whole tree (`./`, `src/..`, `:/`, `:(top)`, `*`), `git commit -a` (bundles too) and `git commit .`; named files, `-A src/`, `-p`, `-i`, `-n` and `--dry-run` are not reported. The same resolution applies, so an alias (`git config alias.ca 'commit -a'`) or a `sh -c` spelling counts. AgentVerdict's git-guardrails profile (`gitPolicies: stage-by-name`) can map to this check instead of a tuned user rule. 57 corpus rows, a policy-level test of the default and the three values, one integration row with the check set in a v2 config file. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…truction and memory files (node9Firewall#413 part 4) A new check, Soft log, Standard review, Strict review on every write, for CLAUDE.md, CLAUDE.local.md, AGENTS.md, GEMINI.md, .cursorrules, .cursor/rules/*, .github/copilot-instructions.md, the memory directory, .claude/agents|commands|skills (project or home) and the hooks of the agent settings files (~/.claude/settings.json, .claude/settings*.json, ~/.gemini/settings.json, ~/.codex/hooks.json, ~/.cursor/hooks.json). These files are loaded into every later session, so a line written there persists (OWASP ASI06); a rewritten hook disarms the guard itself (ASI10). At Standard every write is recorded and the call is held only when: - the written text carries an instruction to hide something from the user, to skip approvals or disarm the guard, or to read or send a secret (dlp/instruction-write.ts: three new signals plus the output scanner's override, fake-role and destination signals, one enough; an imperative context required, a negation in front cancels, quoted mentions and code spans or fenced blocks are left alone, the destination must be real); - the hooks of a settings file change: a Write is compared with the file on disk, an Edit is applied to it first (host hook readAgentSettings, 256 KiB cap); without the file, any mention of hooks counts; - a shell write's content cannot be read from the command (cp, mv, a download), or the file is removed. Strict and Block all ask on every write (levels.ts reviewsEveryAgentFileWrite). The file verdict is combined with the protected-file and hook-file verdicts by strictness inside evaluatePolicy; fw#318 P4 (a write after outside content was read) is untouched. Measured before the patterns joined: 338 of our own agent files (every CLAUDE.md, AGENTS.md and memory file on the development machine, third-party AGENTS.md included, 1.47 MB); the first draft matched 173 times, the shipped rule 6 files, listed in the design. The legitimate corpus test ships our own instruction files as fixtures. 141 engine rows, 5 level rows, 3 integration tests through dist/cli.js with a real settings file in a temp HOME. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Seven of ten findings from /code-review high on the four commits: - A shell write to an agent file whose text is in the command (`echo … > CLAUDE.md`, a heredoc, printf, sed -i, touch, `: > F`) was reviewed as "content cannot be read". shell/write-source.ts reads the statement: only a copy verb (cp, mv, install, rsync, dd, a download, tee or cat fed by a file) is unseen; a command that does not parse stays unseen. - `git config --global alias.st` (a read of the alias) was judged as a git call with an unreadable subcommand; a definition needs a value. - `git -c clean.requireForce=false clean -d` passed: the config the walk already reads now counts as the force. - The narrowed destination regex replaced the output scanner's by array position; replaced by what it matches instead. - The quoted-mention filter was copied from injection.ts: isMention is exported and shared. - resolveShellInput was computed three times per call: once. - A logged file finding replaced a logged shell finding in the one audit row: the command's own finding is kept. Left as they are, with the reason in the design (7g): the scan's missing file-tool half (a separate piece of work), the silent catch in GitWalk.run (a throw would fail the hook open), and the host's own writtenContent in fw#318 P4 (untouched by this issue on purpose). Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…irewall#413 review, finding 5) extractCanonicalFindings had no file-tool half for the file checks the gate runs, so a Write that empties .husky/pre-commit, an Edit of ~/.claude/settings.json that replaces a hook command, or a Write of "do not tell the user" into CLAUDE.md produced no extractor finding while the live gate held each of them. - files/file-checks.ts: evaluateFileChecks runs the protected-file, git-hooks and agent-file checks and keeps the strictest verdict. evaluatePolicy and the extractor both call it. - scan/canonical.ts: file tools emit a 'file-access' finding with the gate's ruleName and a new checkId field. ExtractContext gains cwd, homeDirs and catalogSettings. A logged allow is not a finding; the finding keeps only the path, not the written content. - files/agent-files.ts: settingsUnknown (set by the scan, which has no settings reader) judges a whole settings write by whether its hooks run nothing, instead of reporting every write of a file that has hooks. - daemon/scan-watermark.ts: canonicalContextForLine passes the line's absolute cwd and the home directory. - canonical-v32 log entry with the measured rows; the hashed source set now covers shell/git.ts and the file-check sources; hash regenerated. The CLI `node9 scan` already reported these calls (it judges through the gate). Not covered yet: shell writes to the same files, and toScanFinding still drops 'file-access' before upload. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…line check CI on PR #432 failed on Ubuntu, Windows and CodeQL: - check.integration: the #413 payloads carried no hook_event_name, so the agent was recognised only from CLAUDECODE in the environment. Locally the tests ran inside Claude Code and passed; on CI the review waited for an approver until the spawn was killed (status null). Every payload now names its hook event. - scan-watermark-file-context: the absolute-path row is POSIX only. The file checks match paths on `/`, and the gate's own context passes os.homedir() the same raw way, so on Windows neither side matches. - readAgentSettings (CodeQL js/file-system-race): type and size now come from the open descriptor through readCappedText, which gains an optional open-flags argument; O_NONBLOCK keeps a FIFO at the path from hanging the gate. New spec, including the FIFO row (hangs without the flag). - hookTextRunsNothing (CodeQL js/polynomial-redos): the line is trimmed and the pattern no longer has `\s*` on both sides of an optional group. A 50,000-space line took 715 ms before; the new row bounds it at 200 ms. Extractor hash regenerated (git-hooks.ts is in the hashed set); detector output is unchanged, so canonical-v32 stays. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
On Windows the host's file layer is off: prepareFileAccesses (src/policy/file-access.ts) returns no accesses on win32, so the hook-file and agent-file checks never run there, like the older protected-file checks. The four file-tool rows and `rm .husky/pre-commit` now skip on Windows (itUnix, the pattern other integration specs use); the `--no-verify` and `HUSKY=0` rows come from the command parser and still run everywhere. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
feat: git and agent-file checks (node9Firewall#413) and their file-tool half in the scan
Contributor
🛡️ node9 agent-security · ✅This PR introduces no agent-security findings. 0 finding(s) · · technical details |
Member
Author
|
🎉 This PR is included in version 2.42.0 🎉 The release is available on: Your semantic-release bot 📦🚀 |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Release of node9Firewall#413 (git and agent files), merged to dev in #432.
commands.git-destructiveis decided from the parsed command (shell/git.ts): a resolved variable,git -C, an alias definition andsh -care judged by what runs.commands.git-hooks-bypass:--no-verify,core.hooksPath,HUSKY=0, and a hook file removed, emptied or made non-executable.commands.git-stage-all.files.agent-instructions: writes to CLAUDE.md, AGENTS.md, memory, agents/commands/skills, and changes to the hooks of agent settings. Standard asks only when the text carries an instruction to hide, skip approvals or touch secrets.canonical-v32, so daemons re-scan once).readAgentSettingsreads through the open descriptor with O_NONBLOCK (CodeQL js/file-system-race); the inert-hook-line check is linear (CodeQL js/polynomial-redos).CI on #432 was green on Ubuntu, Windows, coverage, CodeQL and e2e.
🤖 Generated with Claude Code