Merged
Conversation
added 3 commits
September 23, 2026 14:46
…ody text (#432) A tools/00-manual/*.md rule tested `fold_full` -- the whole raw command text, heredoc body included -- against a `~` regex, `require:`, and `forbid:` alike. A heredoc body is a payload piped to whatever the operator line names, not a command, so a word inside it that a rule targets refused a call the word never ran in -- the #7 false-block shape one syntax form over. jit_strip_heredoc_body() (scripts/common.sh) removes every heredoc BODY line -- and its own closing delimiter line -- from the command text before the fold, leaving the heredoc OPERATOR line itself untouched so a rule can still target the command that actually runs on it. Guarded against a here-string (<<<word), which opens no body and must not be mistaken for one. Co-Authored-By: Max <noreply>
Two independent reviewers found three real edges in the first cut of jit_strip_heredoc_body(): - a `<<WORD`-shaped line that never opens a genuine heredoc (an ordinary quoted string containing the shape) left `in_heredoc` set for the rest of the command, silently dropping every real command word after it from `fold_full` -- worse than #432 itself, since nothing blocks and nothing says why. Fixed by requiring a genuine closing delimiter, found by scanning ahead, before anything strips; the same lookahead also closes a CRLF-authored heredoc whose real closing line never matched an LF-only delimiter. - `<<\DELIM` (backslash-quoted, unquoted delimiter) was not recognized at all, so the original #432 bug persisted for this spelling. Recognized now, alongside the single/double-quoted forms. - a heredoc piped to a known interpreter (bash/sh/ssh/python3/...) had its body stripped unconditionally, which would let a forbidden word slip past a forbid:/~/block row inside `bash <<EOF ... EOF` that correctly blocks the same word as a bare command -- the body IS the command there, not a payload. jit_heredoc_targets_interpreter() is a denylist of the concretely-reported cases; that heredoc is left alone. Six new test pairs cover all three, red confirmed against the previous commit and green against this one. Co-Authored-By: Max <noreply>
Logged for the curation pass -- see the fragment for the reproduction and the fix that was actually applied in scripts/common.sh. Co-Authored-By: Max <noreply>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Closes #432
A
~regex rule (andrequire:/forbid:beside it) was tested against the whole Bash command text, heredoc body included. A heredoc body is a payload piped to whatever the operator line names, not a command -- a word inside it that a rule targets tripped the rule on data the call never ran, the same false-positive shape as #7 (a quoted argument mentioning a blocked verb), one syntax form over.Fix
jit_strip_heredoc_body()(scripts/common.sh) removes every heredoc BODY line -- and its own closing delimiter line -- from the command text before the fold that feeds all three matchers (fold_fullin pre-tool-hook.sh), leaving the heredoc operator line itself visible so a rule can still target whatever actually runs on that line. Guarded against a here-string (<<<word) being mistaken for a heredoc.Extended to
require:andforbid:, not only the~arm the issue names: all three read the samefold_full, and satisfying arequire:flag via text that only exists inside a heredoc payload (never a real argument) was itself a latent bypass of the same class #432 reports forforbid:. This closes it symmetrically instead of leaving one side open. Covered by a dedicated require: test row: a real argument still satisfies (not blocked); a heredoc-only mention no longer satisfies (now correctly blocked, "Missing required").Self-review hardening (beyond the issue's own repro)
in_heredocon any<<WORD-shaped line and cleared it only on a later line equal toWORD-- so a false trigger (the shape appearing inside an ordinary quoted string, or a genuinely unclosed heredoc) leftin_heredocset for the rest of the string, silently dropping every command word after it from every rule. Rewrote to scan ahead for a genuine closing delimiter line before stripping anything; no such line anywhere in the rest of the command means nothing is stripped at all, so a false trigger can now only leave MORE of the command visible than a hand parser would, never less.<<\DELIM(backslash-escaped, unquoted delimiter) is standard bash syntax and was not recognized by the operator regex (only single/double-quote-prefixed forms were). Extended the quote-class to include a backslash.bash <<EOF,sh,ssh host,python3, etc.) had its body stripped unconditionally by the first cut -- but that body IS the command that executes, not a payload, so aforbid: rm -rfrow that correctly blocked a barerm -rf /would have silently let it through insidebash <<EOF...EOF. This is a new bypass this fix would otherwise have introduced. Addedjit_heredoc_targets_interpreter(), a denylist of common interpreters/remote-exec commands checked as whole tokens against the operator line; a heredoc targeting one of those is left unstripped. A denylist, not an oracle, same posture this codebase already states out loud for its index-write guard -- a wrapper script the list has never heard of still gets its heredoc body stripped, which is A~Bash rule matches words inside a heredoc body, so a payload that merely mentions a blocked command is refused #432's own residual, not a regression this fix introduces.rest == delimcomparison had no CR-handling, so a CRLF-authored heredoc's real closing line (EOF\r) never matched an LF-onlydelim(EOF), which is the same silent-swallow-everything-after failure as the lookahead finding above, for a Windows-originated data shape. Fixed by the same lookahead: the candidate closing line is CR-trimmed before the delimiter comparison, and independently, the "no genuine close found = strip nothing" invariant means even an untrimmed CR mismatch would fail safe. Not separately covered by a literal-CR-byte test in tests/test-pre-tool-hook.sh (the existing CRLF section there tests rule-FILE content, not command-text CRLF) -- this is a stated coverage gap, defended by code-reading and by the shared lookahead invariant rather than by a dedicated test, left for follow-up given the practical difficulty of embedding a raw CR byte through the JSON/bash/run_hook test-fixture chain.Known limitation (below-bar, not a regression)
cmd <<A <<B(two heredocs chained on one operator line) only has its first heredoc's body stripped; the second remains visible to rules, which is the pre-existing A~Bash rule matches words inside a heredoc body, so a payload that merely mentions a blocked command is refused #432 shape for that one rare construct, not a new regression (A~Bash rule matches words inside a heredoc body, so a payload that merely mentions a blocked command is refused #432).Tests
SECTION 4b in tests/test-pre-tool-hook.sh: 340/343 before wiring
jit_strip_heredoc_body()intofold_full(red, the 3 new assertions), 343/343 after (green). Self-review hardening added 6 more assertions: 347/353 red before, 353/353 green after (verified by stashing scripts/common.sh alone against the already-updated test file). Also green after the final commit: tests/test-security.sh (108/108), tests/test-commands.sh (21/21), tests/test-dogfood-entries.sh (97/97), tests/test-line-citations.sh (6/6). The repository's fullbash tests/run-all.shwas not run by this lane; CI is the authority for that per this repo's own convention.Docs
README.md (the repo's only docs_targets entry) does not document tool-rule matching internals at this level of detail (grepped for heredoc/multi-line/whole-command/full-command/regex-rule -- zero hits); no change needed.
changelog.d/432.fixed.md added;
python3 .oss/assemble_changelog.py --checkpasses.[AI-generated]