Development record
The 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.
The 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.
Grounds
- pursued: a body line ending in an odd count of backslashes followed by the delimiter line keeps the body open, so a substitution after it is read as body and blocks; it would be shown wrong by any shape where bash ends the body at a line the guard does not, or the reverse, with a neutral word on bash 3.2 and 5.3
Frontmatter
| id | iss-2609252214137586 |
|---|---|
| type | issue |
| lifecycle | resolved |
| severity | major |
.abcd/work/issues/resolved/iss-2609252214137586-the-shell-guard-ends-an-unquoted-here.md
open on GitHub ↗ · commit history ↗
mentions1
- dcn-26092500000000172026-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.