Repository navigation
Ignore a Quoted Separator When the Guard Finds a Loop's done - #2249
Conversation
The wait-loop rule found a loop's `do` and `done` by command position alone, and the shell tokens drop their quoting, so a quoted `";"` read as a separator and the word after it as the loop's close. `while true; do echo ";" done; sleep 1; done` therefore closed at its first `done`, its body held no sleep, and the unbounded wait was allowed. `_loop_parts` now takes the quote mask the caller already computes and counts a `do` or `done` only when neither it nor the word before it is quoted. Two self-test rows pin the two halves, each failing when its clause is removed. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
ⓘ Qodo reviews are paused because your trial has ended. Ask your workspace admin to add credits to resume reviews. Manage billing |
|
Important
This repository does not receive automatic reviews because it has fewer than 10 stars. ⚙️ Run configurationConfiguration used: Repository: ptr727/ProjectTemplate/.coderabbit.yaml Review profile: CHILL Plan: Advanced Run ID:
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 |
Codecov Report✅ All modified and coverable lines are covered by tests. Additional details and impacted files@@ Coverage Diff @@
## develop #2249 +/- ##
==========================================
Coverage ? 56.84%
==========================================
Files ? 16
Lines ? 7519
Branches ? 0
==========================================
Hits ? 4274
Misses ? 3245
Partials ? 0
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.
Copilot review overview
🟢 Approval recommended
The change strictly tightens a security guard to close a verified bypass, is limited to one helper plus regression tests, and runtime checks confirm the new denials without over-denying bounded loops.
Review effort: Balanced
Findings: None
What changed in this PR
This PR fixes a bypass in the Claude Code gh-write-guard.py agent-safety hook's unbounded-wait-loop rule. Previously, _loop_parts located a loop's do/done by command position alone, ignoring the quote mask. A quoted ";" unquotes to the same token a real separator does, so a crafted command like while true; do echo ";" done; sleep 1; done closed the loop at its first (argument) done, hid the sleep body from the guard, and allowed an unbounded wait to run. The fix threads the already-computed per-token quote mask into _loop_parts so a do/done counts only when neither it nor the preceding token is quoted.
Changes:
- Added an optional
quotedmask parameter to_loop_partsand an innerkeywordhelper that rejects a quoted keyword or a keyword following a quoted separator. - Passed the existing
maskfrom_unbounded_wait_loopinto_loop_parts(works at both top level and thebash -crecursion depth). - Added two
_WAIT_CASESself-test rows pinning adoneafter a quoted;and a quoteddone, both expected to deny.
| File | Description |
|---|---|
| host-setup/agent-safety/claude/gh-write-guard.py | Makes loop do/done detection quote-aware so a quoted separator or quoted keyword no longer closes a wait loop early, closing a guard bypass; adds two regression self-test cases. |
I verified: _loop_parts has a single caller, which now passes the mask; the mask functions return a per-token list aligned to toks (or None, which safely preserves prior behavior); the backslash-newline-continuation edge noted in the description is unreachable because classify folds continuations to a space before tokenizing; and, by executing the module, that the two new patterns are denied while bounded/allowed loops (timeout ... bash -c, arithmetic/test-bounded, for, non-loop quoted done) remain allowed.
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
## 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)
The wait-loop rule found a loop's
doanddoneby command position alone. The shell tokens drop their quoting, so a quoted";"read as a separator and the word after it as the loop's close.while true; do echo ";" done; sleep 1; donetherefore closed at its firstdone, the body it saw held no sleep, and the unbounded wait was allowed._loop_partsnow takes the quote mask_unbounded_wait_loopalready computes, and counts adoordoneonly when it opens a command and neither it nor the word before it is quoted. Two self-test rows pin the two halves (adoneafter a quoted;, and a quoteddone), and each fails when only its clause is removed.The local strict review raised no finding. It noted one shape that is unreachable today: a backslash-newline continuation directly before
doordonelexes as a quoted newline token, which the new previous-word test would reject.classifyfolds those continuations into a space before it calls the wait-loop check, its only caller, so the hook never sees that shape.Closes on promotion: #2207
🤖 Generated with Claude Code