Repository navigation
Read a Backslash-Newline by Its Quoting in the Write Guard - #3357
Conversation
The split-word and escaped-line-break denies read every backslash-newline in a command naming git or gh, so text that splits no word was denied: a single-quoted body, a Markdown hard line break in a quoted heredoc, and a continuation glued to a command separator such as && or |. A scan now records the context of each backslash-newline. Unquoted, inside double quotes, and in an unquoted heredoc the deny stands, except directly after a command separator. Inside single quotes or quoted heredoc data the outer shell keeps it as text, so the command is classified a second time with those joined, as an inner shell running that text would read them, and denied where that reading is. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The README listed the contexts the denies read, which left out a comment's end, an ANSI-C quote, a substitution, and a quoted heredoc line before its delimiter. It now names the cases the denies skip instead. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A shell running a quoted body ends a comment at a backslash-newline rather than joining the next line into it, so the joined reading alone hid a command after a commented line. The command is now also read with each quoted backslash-newline as a line break, and denied where either reading is. A continuation glued to a command separator is left to the fold, which already reads the same words, so it no longer costs a second reading. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
A recorded local strict-review pass covers head |
|
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 |
The joined and line-break readings were all-or-nothing, while a shell running a quoted body decides each backslash-newline separately. A body holding a comment-ending one and a word-splitting one matched neither. The quoted text is now scanned as the script a shell may run, and a third reading takes each backslash-newline as that scan places it. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
There was a problem hiding this comment.
🟡 Changes recommended
The guard can allow a command when an inner shell mixes continuation readings, and the README needs clarification.
2 open issues
What changed in this PR
This pull request narrows write-guard denials for backslash-newlines by recognizing shell quoting, heredocs, comments, separators, and nested shell readings.
Changes:
- Adds context-aware scanning and alternate backslash-newline readings.
- Recursively reclassifies quoted heredoc and nested shell-body content.
- Updates the safety README and expands regression coverage.
| File | Reviewed changes |
|---|---|
host-setup/agent-safety/README.md |
Documents the revised quoting and heredoc behavior. Nit (P2): Clarify command-substitution and ANSI-C exceptions to match the scanner. |
host-setup/agent-safety/claude/gh-write-guard.py |
Implements contextual scanning, recursive readings, and regression tests. Critical (P1): Preserve independent inner-shell readings for each literal continuation. |
Review effort: Lite
Codecov Report✅ All modified and coverable lines are covered by tests. Additional details and impacted files@@ Coverage Diff @@
## develop #3357 +/- ##
============================================
+ Coverage 65.53% 81.32% +15.78%
============================================
Files 18 18
Lines 10036 10045 +9
============================================
+ Hits 6577 8169 +1592
+ Misses 3459 1876 -1583
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:
|
The per-span reading resolved one shell level, so a span holding further quoting, or a single-quoted part joined to other quoted parts of its word, could hide a split word from every reading. A span is now kept as text only where its word has no other quoted part, and, where it names git or gh, it holds no further quoting and each backslash-newline sits in its command text or ends a comment there. Any other span is read as develop reads it. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
An escaped space before a single-quoted span joins the text before it into the same word, so the span's own scan misreads the script the inner shell runs. Such a span is no longer kept as text. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
A recorded local strict-review pass covers head |
…dows Self-Test Skip, and Subprocess Failure Rule to Main (#3359) ## Summary - Skip the invalid-byte audit self-test probe when Windows Git cannot add the name, so the self-test no longer fails on a host that cannot represent the path (#3350). The skip fires only on that one add, after a control file's add has proven git works (#3360). - Read a backslash-newline in the write guard by its quoting: inside single quotes and quoted heredoc data it is text, and a continuation after a shell operator is no word split (#3357). - Keep each escaped backslash pair before a kept line break in the guard's readings, as the shell does, and state the kept-span word and names tests in full in the README and docstring (#3368). - Add a `CODESTYLE.md` General rule: classify a subprocess failure by which command failed and the status code it reports, never by the wording of its message (#3366). Closes #3338 Closes #3358 Closes #3363 Refs #3344, which stays open because only part of it is fixed here. #3358's Markdown-body finding moves to #3354. 🤖 Generated with [Claude Code](https://claude.com/claude-code)


Summary
Narrows the write guard's backslash-newline denies, which since the last round read every backslash-newline in a command naming git or gh, quoting and heredoc data included.
_line_break_contexts, maps each backslash-newline to its context.&&,||,|,|&,;,&) and a non-operator character splits no word, so it is left to the fold._literal_span_keptkeeps such a span as text only where the hook can read it as a shell running it would. A single-quoted span must be its word's only quoted, escaped, or expanded part. A span naming git, gh, or a push must hold no further quoting, with each backslash-newline in its own command text or ending a comment there. A span not kept is read as develop reads it.Refs #3344. This fixes these items from #3344:
"$(cat <<'EOF' ... EOF)", andgit commit -F- <<'EOF'data ending in\._SPLIT_WORDmatching an operator glued to a line continuation,git add x &&\andgit log --oneline |\.The other items in #3344 are not addressed here.
Verification
$'...', an&\-newline-&split operator, a redirection operator, single-quoted shell bodies (joined, escaped backslash, comment-ending, mixed comment-ending and splitting), quoted heredocs fed to a shell, a further quoting level, a'\''-joined word, and a span after an escaped word break.test_install.pypass toclassify, plus constructed probes from this lane and its local review passes, classified under develop's guard and this branch's with identical offline seams. 10560 distinct commands: 3889 deny/deny, 6591 allow/allow, 78 deny to allow, 2 allow to deny, 0 errors.test_install.pycorpus flips only on this branch's seven new allow cases.bash -cbody, which develop allowed while denying its plain form.test_install.py,prose_lint.py(diff scope, sentence-length included),repo_gate.py --check eolandeol-coverage,spec/validate.py, ruff, and the markdownlint, cspell, and editorconfig-checker Docker linters are clean.Known Remaining Findings
The local review reached its cap of six passes. The last two passes stopped before probing, so their probe phase ran in the lane itself over the separator and kept-span paths, with no new flip. These introduced findings remain, each a wording or over-deny item rather than an allow. Each is filed in #3358, since a fix push would need a pass past the cap:
_literal_span_kept's summary line reads as if every kept span is read as its inner scan places it, which its later lines qualify for a span naming no git or gh.<<is read as develop reads it. A Markdown PR body mentioninggitin a code span and holding a hard line break is still denied, as on develop.Filed
Pre-existing gaps the local review found, each allowed on develop in its plain form: #3351 (a
cdthat does not lead the command, for rule 6), #3352 (atrapaction string), #3353 ()#and<<-tab stripping), #3355 (a comment-ending backslash-newline at top level, and anevalbody, for rule 6), #3356 (text piped into a shell, or produced by$(...)as a-cbody). Design: #3354 (consolidate the guard's context scanners).#3341 Confirmation
BASH_BIN="$(command -v bash)"; git push --tags originandbash -c 'command -v sh'; git push --tags originboth classify allow on develop 201cbe7 with offline seams.🤖 Generated with Claude Code