Skip to content

Deny Include Overrides on Commits, Command-Running Subcommands, and Non-Builtins, and Read a Local Branch Name Whole in the Write Guard - #2974

Merged
ptr727 merged 5 commits into
developfrom
feature/guard-include-overrides
Oct 14, 2026
Merged

ptr727 merged 5 commits into
developfrom
feature/guard-include-overrides

Conversation

@ptr727

@ptr727 ptr727 commented Oct 14, 2026 •

Copy link
Copy Markdown
Owner

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.

  • An include override on a commit or push (Deny an Include Override on a Commit as a Hooks-Path Bypass in the Write Guard #2958). An include.path/includeIf override on git commit or git push now denies as a hooks-path override. The file it pulls in may set core.hooksPath where the guard cannot read it. -c core.hooksPath= was already denied the same way.
  • An include override on a command that runs other git commands (Deny an Include Override on a Git Command That Runs Other Git Commands in the Write Guard #2960). The override reaches every child git command, so on rebase, submodule, bisect, filter-branch, difftool, or mergetool it is unreadable whatever the arguments say. An eval argument 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.
  • A local branch name and a non-builtin command (Read a Local Branch Name Whole and an Exec-Path Beside an Include Override in the Write Guard #2962).
    • _read_branch keeps a local branch name whole, such as release/1.0, and also checks the stripped name develop read. The remote-tracking @{push} read is unchanged.
    • Under an include override, only a command git builds in is 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 file defines. That makes a separate --exec-path check redundant, so none is kept.
  • Same-class reads found in local review. An include key in a wrapper argument the guard does not read (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.0 worktree 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.

  • Real-repository mode: 7,214 constructed commands. They cross 15 global-option sets (include, includeIf, --config-env, mixed case, --exec-path before 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, eval single-quoted, double-quoted, --, chained, bash -c, sh -c, eval inside a wrapper, a wrapper inside eval, &&, ;, a leading cd, and timeout. The extras are PATH and GIT_EXEC_PATH prefixes, 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 on feature/example, release/1.0, x/develop, and release/2.0 with a real upstream. They ran under six rule sets, one of which protects only the stripped names 1.0 and example. That is 216,420 calls per guard with no current-branch seam.
  • Seam mode: the same corpus under 7 current-branch seams, 5 rule seams, and the real and a pinned command list (504,980 calls).
  • Self-test replay: every classify call the branch self-test makes (845).
  • Rule 6: the corpus with a primary-checkout lookup (7,214 calls).

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:

  • 568: an include override on a command git does not build in (instaweb, request-pull, mergetool, filter-branch, and submodule, a script in git 2.47), plain or inside a now-read eval.
  • 448: an include override on a builtin command-running subcommand (rebase, bisect, difftool), rebase --continue included.
  • 209: an include override on a name git does not list, inside an eval this branch now reads.
  • 65: a bare or HEAD push from the release/1.0 worktree, only under the rule set protecting release/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.
  • 58: an include override on a commit.
  • 4: an include key in a wrapper body past the nesting cap.
  • 1: an include key in an unread wrapper argument.
  • 1: an include key after a spent eval budget (a row of 300 evals).

Seam mode adds one more class. An eval payload is now read under the pinned command list, which lacks log and fetch, so it denies there as an unlisted name. Rule 6 shows the same classes. A row of 20,000 eval x; before a push classifies in 1.46s on this branch against 1.27s on develop, both deny.

Known New Over-Denials

  • Any include override on a commit or push.
  • An include override on any command git does not build in, even where git would run the real command rather than an alias.
  • An include override on rebase, submodule, bisect, filter-branch, difftool, or mergetool, whatever its arguments.
  • An include key in a wrapper's unread argument, or in a body past the nesting cap, even where it sits on a listed command.
  • After about 256 evals spend the reading budget, any include key later in that text.
  • An eval that is only an argument, as in echo 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

Local Review

Three local strict-review passes ran at the frontier tier. 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

ptr727 and others added 3 commits October 13, 2026 19:48
…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>
Copilot AI lite review requested due to automatic review settings October 14, 2026 03:25
@coderabbitai

coderabbitai Bot commented Oct 14, 2026 •

Copy link
Copy Markdown

Important

  • 🔍 Trigger review

This repository does not receive automatic reviews because it has fewer than 10 stars.

⚙️ Run configuration
  • Configuration used: Repository: ptr727/ProjectTemplate/.coderabbit.yaml
  • Review profile: CHILL
  • Plan: Advanced
  • Run ID: b655a701-7013-43e0-a931-f4737bac5254
  • Autofix · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

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.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@ptr727

ptr727 commented Oct 14, 2026

Copy link
Copy Markdown
Owner Author

A recorded local strict-review pass covers head 30db93e6a4c0a9318df35c9e866c24817723fee6, the content this pull request carries at that commit against develop, and it recorded 1 finding.

@codecov

codecov Bot commented Oct 14, 2026 •

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 60.53%. Comparing base (d2ed08c) to head (7235408).
⚠️ Report is 1 commits behind head on develop.

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     
Flag Coverage Δ
python-3.13 60.53% <ø> (+0.50%) ⬆️

Flags with carried forward coverage won't be shown. Click here to find out more.

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔵 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.

Comment thread host-setup/agent-safety/README.md Outdated
Comment thread host-setup/agent-safety/claude/gh-write-guard.py
ptr727 and others added 2 commits October 13, 2026 20:34
…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>
@ptr727

ptr727 commented Oct 14, 2026

Copy link
Copy Markdown
Owner Author

A recorded local strict-review pass covers head 7235408397efc7a235764eb246ac91108127f4d4, the content this pull request carries at that commit against develop, and it recorded 1 finding.

@ptr727
ptr727 merged commit e98def1 into develop Oct 14, 2026
13 checks passed
ptr727 added a commit that referenced this pull request Oct 14, 2026
…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 -->
@ptr727
ptr727 deleted the feature/guard-include-overrides branch October 14, 2026 13:49
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants