Skip to content

End the Guard's Stdin Redirect Scan at a Trailing Comment - #2227

Merged
ptr727 merged 3 commits into
developfrom
feature/auto-2152
Oct 1, 2026
Merged

ptr727 merged 3 commits into
developfrom
feature/auto-2152

Conversation

@ptr727

@ptr727 ptr727 commented Oct 1, 2026

Copy link
Copy Markdown
Owner

Summary

The wait-loop rule's _redirects_stdin scan credited a redirect written inside a trailing comment as the loop's input bound, so yes | while read l; do sleep 30; done # < f (and the #< f spelling) was allowed although bash reads nothing after the #.

  • The scan now ends at a word opening an unquoted comment, the way it already ends at a separator or a reserved word. The quoted mask the rule already computes says which # words are quoted, so done < '#f' still names a file.
  • A comment keeps an earlier binding only where the scan is sure it is one: the quoting is known and no expansion that can hold a space precedes it. Otherwise ending early skipped the later binding bash applies, as in < ${g:- #x} < /dev/zero.
  • A redirect whose target opens a comment has no target, so it bounds nothing.
  • A command holding a carriage return passes no mask, since the lex splits words at one and bash does not, so log<CR>#x is one word rather than a comment.
  • New --selftest cases cover each shape. Reverting the fix with the cases kept fails the new deny cases, and passing no mask fails the quoted-# allow case.

A local strict review pass also found a pre-existing sibling, a separator or parenthesis inside a target expansion ending the scan early, filed as #2225.

Closes on promotion: #2152

🤖 Generated with Claude Code

ptr727 and others added 3 commits October 1, 2026 00:32
The wait-loop rule credited a redirect written inside a trailing comment
as the loop's input bound, so `yes | while read l; do sleep 30; done # < f`
was allowed although bash reads nothing after the `#`. The scan now ends
at a word opening an unquoted comment, and at a redirect target that opens
one, using the quoted mask so a quoted `#` still names a file.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Ending the scan at a `#` it could not be sure was a comment skipped the
later binding that applies, as in `< ${g:- #x} < /dev/zero` or a quoted
`'#x'` whose quoting the mask could not read, both allowed although
develop denied them. A comment now keeps an earlier binding only where the
quoting is known and no expansion precedes it, and a redirect whose target
opens a comment bounds nothing.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The lex splits words at a carriage return and bash does not, so in
`log<CR>#x` the `#` sits inside a word yet arrived as a token of its
own, ending the scan before a later `< /dev/zero`. The caller now passes
no mask for such a command, so no comment keeps an earlier binding.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Copilot AI balanced review requested due to automatic review settings October 1, 2026 07:59
@codecov

codecov Bot commented Oct 1, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
⚠️ Please upload report for BASE (develop@3c13429). Learn more about missing BASE report.

Additional details and impacted files
@@            Coverage Diff             @@
##             develop    #2227   +/-   ##
==========================================
  Coverage           ?   56.61%           
==========================================
  Files              ?       16           
  Lines              ?     7482           
  Branches           ?        0           
==========================================
  Hits               ?     4236           
  Misses             ?     3246           
  Partials           ?        0           
Flag Coverage Δ
python-3.13 56.61% <ø> (?)

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.

Copilot review overview

🔵 Needs a closer look

It modifies a security-critical agent-safety guard whose correctness depends on subtle shell-tokenization and quoting-mask behavior, warranting final human verification despite no defects being found.

Review effort: Balanced
Findings: None

What changed in this PR

This PR fixes a false-negative in the hub's Claude write guard (host-setup/agent-safety/claude/gh-write-guard.py). The _redirects_stdin scan, which decides whether a while read ... done wait loop is bounded by a finite input, previously credited a redirect written inside a trailing shell comment (e.g. ... done # < f or ... done #< f) as the loop's input bound. Since bash reads nothing after #, such a loop actually drains its upstream pipe forever and should be denied. The guard is part of the fleet's agent-safety layer that blocks unbounded poll loops, so this closes a real bypass.

The fix ends the scan at an unquoted-# word the same way it already ends at a separator or reserved word, using the quoting mask the caller already computes to distinguish a real comment from a quoted #f filename, and it is deliberately deny-safe where quoting is unknown or an expansion that can hold a space precedes the #.

Changes:

  • Add a quoted mask parameter to _redirects_stdin/_reads_its_input and thread the existing per-token mask from _unbounded_wait_loop (passing None when the command contains a carriage return, which the lexer splits on but bash does not).
  • End the redirect scan at an unquoted # word, treat a redirect whose target opens a comment as unbound, and only carry an earlier binding past a # when the comment is certain.
  • Add nine --selftest cases covering trailing/glued comments, comment-as-target, payload comments, quoted #, # inside an expansion, unknown quoting, and the carriage-return shape.
File Description
host-setup/​agent-safety/​claude/​gh-write-guard.py Ends the stdin-redirect scan at an unquoted trailing comment and threads a quoting mask so a quoted # still names a file, plus selftest coverage for each shape.

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

@ptr727
ptr727 merged commit 901d886 into develop Oct 1, 2026
10 checks passed
@ptr727
ptr727 deleted the feature/auto-2152 branch October 1, 2026 08:09
ptr727 added a commit that referenced this pull request Oct 1, 2026
## Summary

Promotes develop to main, carrying the pull requests below, each already
reviewed and merged into develop.

- [#2203](#2203): Strip
every heredoc body a line opens in the wait-loop rule.
- [#2216](#2216): Point
the charset-unknown message at where a tier is classified.
- [#2218](#2218): Name the
leftover old tree when the Bash loader cannot remove it.
- [#2227](#2227): End the
guard's stdin redirect scan at a trailing comment.
- [#2230](#2230): Tell
apart the causes a labels payload is refused for.
- [#2232](#2232): Correct
the task names and pointers in the Python tasks snippet header.
- [#2234](#2234): Drop the
stale private-repository note from PhotoCleaner's registry entry.
- [#2237](#2237): State
the bootstrap's archive live channel in two skills.
- [#2239](#2239): Stop the
promotion count on a failed fetch in backlog-burndown.
- [#2241](#2241): Render
the include walk once per `build_dist.py --check` run.
- [#2243](#2243): Have the
tree check call `escapes_repo_root` and refuse a symlinked component.
- [#2247](#2247): Refuse a
Windows drive component anywhere in a tree path.
- [#2222](#2222): Tick the
adopted repos in the merge-bot and gate rollout stages.
- [#2121](#2121): Refuse a
dangling remote-tracking target in `local_review`.
- [#2249](#2249): Ignore a
quoted separator when the guard finds a loop's `done`.

Closes #2112
Closes #2171
Closes #1790
Closes #2152
Closes #1372
Closes #2149
Closes #1082
Closes #1772
Closes #1310
Closes #1379
Closes #1452
Closes #2244
Closes #2168
Closes #2106
Closes #2207

🤖 Generated with [Claude Code](https://claude.com/claude-code)
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