Repository navigation
Deny Include Overrides on Commits, Command-Running Subcommands, and Non-Builtins, and Read a Local Branch Name Whole in the Write Guard - #2974
Conversation
…d Read a Local Branch Name Whole in the Write Guard An include.path or includeIf override on a commit or push now denies as the hooks-path override it may be, since the file it pulls in can set core.hooksPath where the guard cannot read it. The same override on rebase, submodule, bisect, filter-branch, difftool, or mergetool is unreadable whatever its arguments say, and so is one beside an --exec-path or a GIT_EXEC_PATH. An eval argument holding an include key is read as the command it runs. A bare push with no push target, and a HEAD refspec, are now checked under the local branch's whole name as well as the stripped name develop's guard read, so a local release/1.0 is checked against the rules for release/1.0. Each fix adds a reading or a deny beside develop's, and the self-test carries cases for each. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…Under an Include in the Write Guard Under an include override, only a command git builds in is now read as one no alias can shadow. An --exec-path, a GIT_EXEC_PATH, or a PATH change can hand any other name to an alias the included file defines, so this one reading replaces the narrower --exec-path check. An include key in a wrapper argument the guard does not read, or in a wrapper body past the nesting cap, now fails closed. The eval reading skips to each payload's end and reads with the quote mask, bounded by the wait rule's eval budget, so a chain or a row of evals is read once per payload. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…Case Labels to Its Readings Once the eval reading budget is spent, the rest of the text is searched for an include key a single time and no later eval in it is read, so a long row of evals stays linear. The docstring names the wrapper-argument and overflow include reads, the exec-path and PATH case labels describe the builtins reading that decides them, and the README counts the three hook-bypass denies it names. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
Important
This repository does not receive automatic reviews because it has fewer than 10 stars. ⚙️ Run configuration
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
|
A recorded local strict-review pass covers head |
Codecov Report✅ All modified and coverable lines are covered by tests. Additional details and impacted files@@ Coverage Diff @@
## develop #2974 +/- ##
===========================================
+ Coverage 60.02% 60.53% +0.50%
===========================================
Files 17 17
Lines 8784 8904 +120
===========================================
+ Hits 5273 5390 +117
- Misses 3511 3514 +3
Flags with carried forward coverage won't be shown. Click here to find out more. ☔ View full report in Codecov by Harness. 🚀 New features to boost your workflow:
|
There was a problem hiding this comment.
🔵 Needs a closer look
Security-sensitive command parsing still has a false denial and an overstated protection claim, so the guard needs changes and final human validation.
2 open findings
What changed in this PR
This PR tightens the Claude write guard so included Git configuration cannot hide a hook bypass or push alias, and so branch-rule checks retain full local branch names.
Changes:
- Denies include overrides on commits, pushes, command-running Git operations, and non-builtins; extends reading of eval and wrapper text.
- Checks both full and stripped local branch names, with new self-test cases and documentation.
| File | Description |
|---|---|
host-setup/agent-safety/README.md |
Describes the expanded guard rules. |
host-setup/agent-safety/claude/README.md |
Updates the Claude kit's guard description. |
host-setup/agent-safety/claude/gh-write-guard.py |
Implements the denials, branch reading, and self-tests. |
🧠 Review effort: Lite
💡 Configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
…t Reads The explicit-bypass flags and the include override are read in a command the guard parses, a wrapper shell's body among them, and not inside an eval argument, so the READMEs now say so rather than claiming every commit carrying one is denied. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ves Unread An unquoted eval argument is more tokens the check reads, so only a quoted one goes unread. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
A recorded local strict-review pass covers head |
…de-Override and Shell-Rewrite Denies to Main (#2980) - #2970 adds a `cleanup` subcommand to `scripts/pr_review.py` that runs the merged-feature-branch verify-then-delete in code. It reads the head branch from the merged pull request, refuses a base other than `develop`, a head from another repository, a protected or default branch, and a repository outside the registry, reads the ref by its qualified name, deletes only when its tip matches `headRefOid`, and confirms the delete by reading the ref back. `drive-pr` step 4 and `merge-and-release` step 8 now call it, with the hand form kept as the fallback - #2974 makes `gh-write-guard` deny an `include.path`/`includeIf` override on a commit or push, on a command that runs other git commands (`rebase`, `submodule`, `bisect`, `filter-branch`, `difftool`, `mergetool`), and on any command git does not build in. It reads an `eval` argument holding an include key as the command it runs, and keeps a local branch name such as `release/1.0` whole when checking it against the rule set. A differential against the previous guard found zero allow flips, and the known new over-denials are listed in that pull request - #2979 makes `gh-write-guard` read quote-split words and an uppercase `GIT` through the existing push and include checks, and fail closed on a run-time word (an expansion, a substitution, a backtick, a brace expansion) where the subcommand goes, among a push's or a commit's global options or arguments, on a redirection before the subcommand, and on an unquoted comment among a push's words. The managed permission rule's description now names `pr_review.py cleanup`. A differential against the previous guard found zero allow flips, and the known new over-denials and the gaps left open (#2976, #2977, #2978) are listed in that pull request After this merges, the host-safety installer payload changes, so the maintainer runs the installer after promotion. Closes #2967 Closes #2958 Closes #2960 Closes #2962 Closes #2959 Closes #2961 Closes #2968 🤖 Generated with [Claude Code](https://claude.com/claude-code) <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit * **New Features** * Added a guarded cleanup option for merged pull request branches. It verifies that the branch is eligible and unchanged before deletion, then confirms the result. * **Security** * Strengthened protections against potentially unsafe Git commit and push commands, including attempts to bypass configured safeguards. * **Documentation** * Updated guidance for branch cleanup, safety checks, and the outcomes reported when cleanup cannot proceed. <!-- end of auto-generated comment: release notes by coderabbit.ai -->


Summary
This closes three allow gaps in
host-setup/agent-safety/claude/gh-write-guard.py. Each fix adds a reading or a deny beside develop's and replaces none of develop's readings.include.path/includeIfoverride ongit commitorgit pushnow denies as a hooks-path override. The file it pulls in may setcore.hooksPathwhere the guard cannot read it.-c core.hooksPath=was already denied the same way.rebase,submodule,bisect,filter-branch,difftool, ormergetoolit is unreadable whatever the arguments say. Anevalargument holding an include key is now read as the command it runs, for the push check. The reading uses the quote mask and skips to each payload's end. It is bounded by the eval budget the wait rule already uses, and an include key met after that budget is spent fails closed._read_branchkeeps a local branch name whole, such asrelease/1.0, and also checks the stripped name develop read. The remote-tracking@{push}read is unchanged.--exec-path, aGIT_EXEC_PATH, or a PATH change can hand any other name to an alias the file defines. That makes a separate--exec-pathcheck redundant, so none is kept.bash -c '$1' _ '...'), or in a wrapper body past the nesting cap, now fails closed.The self-test carries discriminating cases for each one: develop allows them and this branch denies them. A
release/1.0worktree in the real-repository push cases exercises the branch read. The agent-safety README's requirement 4 and the Claude kit README state the new denies.Differential Against Develop's Guard
Develop's guard (the merge base, d2ed08c) and this branch's guard were loaded side by side and run over the same calls.
--config-env, mixed case,--exec-pathbefore and after an include,-C,core.hooksPath, an inline alias) with 35 subcommands. Those cover push heads, commit, listed and unlisted names, non-builtins, every command-running subcommand, and fetch and merge. Each sits in 14 wrappers: plain,evalsingle-quoted, double-quoted,--, chained,bash -c,sh -c, eval inside a wrapper, a wrapper inside eval,&&,;, a leadingcd, andtimeout. The extras are PATH andGIT_EXEC_PATHprefixes, rows of 3 to 300 evals, an unread wrapper argument, and wrapper bodies 3 to 7 levels deep. Each command ran in a constructed repository's develop checkout and in worktrees onfeature/example,release/1.0,x/develop, andrelease/2.0with a real upstream. They ran under six rule sets, one of which protects only the stripped names1.0andexample. That is 216,420 calls per guard with no current-branch seam.classifycall the branch self-test makes (845).Allow flips (develop denies, this branch allows): zero in every mode.
Every deny flip, by the reason the branch gives, from real-repository mode's 1,354 distinct flipped commands:
instaweb,request-pull,mergetool,filter-branch, andsubmodule, a script in git 2.47), plain or inside a now-readeval.rebase,bisect,difftool),rebase --continueincluded.evalthis branch now reads.HEADpush from therelease/1.0worktree, only under the rule set protectingrelease/1.0. These are the Read a Local Branch Name Whole and an Exec-Path Beside an Include Override in the Write Guard #2962 fix.Seam mode adds one more class. An
evalpayload is now read under the pinned command list, which lackslogandfetch, so it denies there as an unlisted name. Rule 6 shows the same classes. A row of 20,000eval x;before a push classifies in 1.46s on this branch against 1.27s on develop, both deny.Known New Over-Denials
rebase,submodule,bisect,filter-branch,difftool, ormergetool, whatever its arguments.evalthat is only an argument, as inecho eval 'git -c include.path=/example/config p', is read as one that runs. Narrowing it is filed as Read Only an Eval That Runs in the Write Guard's Include Reading #2975.Left Open Deliberately
core.hooksPathoverride on a commit inside a quotedevalargument still passes the hook-bypass check, which does not read one. That is the Close the Write Guard's Remaining Push Detection Gaps Found Reviewing the #2936 Fix #2943 class, and both READMEs now say so rather than claiming every such commit is denied.bash -c '$1 $2' _ git '-c include.path=...', is the Read Words the Shell Rewrites Before a Push in the Write Guard #2959 word-rewrite class.core.fsmonitor,diff.external,core.sshCommand,credential.helper) set inline or by an included file, on any git command, is filed as Read a Config Key That Runs a Command on Any Git Command in the Write Guard #2972. It is the wider form Deny an Include Override on a Commit as a Hooks-Path Bypass in the Write Guard #2958's comment raised.remotes/, is filed as Strip Only the Remote From a Remote-Tracking Push Target in the Write Guard #2973.Local Review
Three local strict-review passes ran at the
frontiertier. None found an allow flip against develop. Rounds one and two found same-class bypasses, an eval-chain false deny, a quadratic eval path after the budget, consolidation points, and doc claims, each fixed in the second and third commits. The deferred findings are the issues above. The third pass raised one sentence-length note on a docstring line, which is declined, since that check is in no gate and the sentence is a defensible form. Two more passes covered the README qualification Copilot asked for.After merge, the host-safety installer payload changes. The installer is not run here, and the maintainer runs it after promotion.
Closes #2958
Closes #2960
Closes #2962
🤖 Generated with Claude Code