Development record
2026-09-25 — Correction to the round-4 entry above on the shell guard's here-document bodies, with three further rulings (fix round 5 of lane guard, run A, closing review5-guard). Ruling (b) of that entry read an unquoted body line by line, and the round's report held that bash can only end such a body later than the guard, so the guard over-reads and never under-reads. That is false: in a body whose delimiter is unquoted, bash joins a physical line ending in an odd number of backslashes with the next line before it compares the delimiter, so `x\` followed by `EOF` does not end the body, and ending it there read the rest as command text, where a single-quoted span or a `#` hid a substitution the body runs (iss-2609252214137586). A later end read with a different grammar is an under-read. The body is now read by the logical lines bash compares: joined across an odd trailing run of backslashes, for an unquoted delimiter only; an even run and a quoted delimiter join nothing, and a `<<-` body strips the leading tabs of the joined line alone. (a) Inside double quotes a `${…}` ends at its own `}`, found as bash's parser finds it: a `"` in it opens a nested string, a `'` pairs with the next `'` for the parse only (the substitutions between them still run, and are followed), a bare `{` opens nothing. `echo "${MSG:-"don't"}"` and its kin, which blocked as `command-unparsable` after round 4, run; a `${` that never closes leaves the string as the text it was, and BLOCK stays the answer for a line the tokenizer cannot split (iss-2609252214217550). (c) A word that is wholly `"$(cat <<DELIM … DELIM)"`, whose body the shell does not change (a quoted delimiter, or an unquoted one with no `$`, backtick or backslash in it), is ALSO read as the document's text wherever it is a payload (`sh -c`, `eval`, `su -c`, `env -S`), beside the unknown reading, which keeps every signal it raises: a clean document still warns as an uninspectable payload, and a hazard in the document blocks under its own entry. Replacing the unknown reading was weighed and not taken, because a `cat` redefined earlier in the same line would print something else, and an additive reading cannot weaken a verdict (iss-2609252214215409). (d) One more accepted over-block, named so it is not mistaken for a defect: a here-document whose delimiter is a substitution (`cat <<$(echo EOF)`) is not run to learn the delimiter, so no line ends the document and the line blocks as `heredoc-unterminated`, although bash runs it; writing the delimiter out is the way past.
2026-09-25 — Correction to the round-4 entry above on the shell guard's here-document bodies, with three further rulings (fix round 5 of lane guard, run A, closing review5-guard). Ruling (b) of that entry read an unquoted body line by line, and the round's report held that bash can only end such a body later than the guard, so the guard over-reads and never under-reads. That is false: in a body whose delimiter is unquoted, bash joins a physical line ending in an odd number of backslashes with the next line before it compares the delimiter, so x\ followed by EOF does not end the body, and ending it there read the rest as command text, where a single-quoted span or a # hid a substitution the body runs (iss-2609252214137586). A later end read with a different grammar is an under-read. The body is now read by the logical lines bash compares: joined across an odd trailing run of backslashes, for an unquoted delimiter only; an even run and a quoted delimiter join nothing, and a <<- body strips the leading tabs of the joined line alone. (a) Inside double quotes a ${…} ends at its own }, found as bash's parser finds it: a " in it opens a nested string, a ' pairs with the next ' for the parse only (the substitutions between them still run, and are followed), a bare { opens nothing. echo "${MSG:-"don't"}" and its kin, which blocked as command-unparsable after round 4, run; a ${ that never closes leaves the string as the text it was, and BLOCK stays the answer for a line the tokenizer cannot split (iss-2609252214217550). (c) A word that is wholly "$(cat <<DELIM … DELIM)", whose body the shell does not change (a quoted delimiter, or an unquoted one with no $, backtick or backslash in it), is ALSO read as the document's text wherever it is a payload (sh -c, eval, su -c, env -S), beside the unknown reading, which keeps every signal it raises: a clean document still warns as an uninspectable payload, and a hazard in the document blocks under its own entry. Replacing the unknown reading was weighed and not taken, because a cat redefined earlier in the same line would print something else, and an additive reading cannot weaken a verdict (iss-2609252214215409). (d) One more accepted over-block, named so it is not mistaken for a defect: a here-document whose delimiter is a substitution (cat <<$(echo EOF)) is not run to learn the delimiter, so no line ends the document and the line blocks as heredoc-unterminated, although bash runs it; writing the delimiter out is the way past.
Frontmatter
| id | dcn-2609250000000017 |
|---|---|
| type | decision |
| date | 2026-09-25 |
.abcd/work/decisions/dcn-2609250000000017-correction-to-the-round-4-entry-above-on.md
open on GitHub ↗ · commit history ↗
mentions3
- iss-2609252214137586The shell guard ends an unquoted here-document body at a line that bash joins onto the line before it. In a body whose delimiter is unquoted, bash joins a physical line ending in an odd number of backslashes with the next line before it compares the delimiter (read_secondary_line with its backslash-newline splice), so x-backslash then EOF is the one body line xEOF and the body goes on. skipHeredocBodies (internal/core/guard/tokenize.go) compared each physical line, ended the body at that EOF, and read what followed as command text, where a single-quoted span or a # comment hides a substitution the body runs: a blocked command in $( ) on the next line allowed, in the plain, <<- and three-backslash twins (review5-guard finding 1, verified on bash 3.2 and 5.3 with a neutral word). The fix report's claim that the guard can only over-read such a body is false.
- iss-2609252214215409A shell or eval payload written as a here-document substitution is not read, though its text is the payload verbatim. sh -c "$(cat <<EOF … EOF)" and eval "$(cat <<'EOF' … EOF)" carry the document's text as the string the shell runs, but the tokenizer leaves a substitution's output unknown, so the payload is uninspectable and the family's loud WARN is the verdict: a blocked command in the document runs (review5-guard finding 3). Where the output is fixed — cat with nothing but the here-document, a quoted delimiter or a body with no expansion in it — the document's text can be read as the payload as well as the unknown output.
- iss-2609252214217550The shell guard blocks valid bash as command-unparsable when a double-quoted ${…} carries double quotes of its own. Inside double quotes bash parses a parameter expansion to its own closing brace: a double quote in it opens a nested string instead of closing the outer one, and a single quote pairs with the next. The tokenizer's double-quote branch and closingDoubleQuote (internal/core/guard/tokenize.go) ended the string at the first nested quote, so an apostrophe inside the nested quotes read as an unterminated single quote: echo "${MSG:-"don't"}", "${1:-"it's"}", printf '%s\n' "${NAME:-"O'Brien"}" and "${X//"'"/x}" returned ErrUnparsableCommand, which the pre-tool-use hook (internal/surface/cli/guard.go) now blocks as command-unparsable, where the round before they ran (review5-guard finding 2).