Development record
2026-09-25 — Correction to the entry above on the shell guard's unknown word (fix round 3 of lane guard, run A, closing review3-guard). Its mark was not unforgeable as `internal/core/guard/unknown.go` claimed: an ANSI-C escape that decodes to NUL (`$'\x00'`, `$'\0'`, `$'\u0000'`) put the mark into a word after Check had dropped the line's own NUL bytes, and a blocked command's name behind one allowed (iss-2609252020432185). It is unforgeable by construction now: Check drops the line's NULs, and an ANSI-C string ends at its first decoded NUL, as bash ends it. An out-of-band flag on the word was weighed and not taken, because every reader in the package holds words as strings and the change would not have been contained. The same round makes the rule total: every reader of a word or a command name reads through unknown.go, an unknown dash-word is read both as a flag that stands alone and one that takes a value (and as a shell's `-c` or a verb's payload flag) before command position as after it (iss-2609252020507464), and a substitution in command position is every program its known tail allows, a shell, a wrapper and git among them, which resolves the command-position half of iss-2609251824244354; that record now names only the parameter-expansion half (`--$X`, `${…}`), still deferred with its reason, so allow (4) above is that half alone. A test parses the package and fails on a reader of a word's dash or a command's name that is not listed with how it reads the rule. Four over-blocks are accepted in the fail-closed direction, each recorded so it is not mistaken for a defect. (1) An unknown operand can be any subcommand, so a read-only command whose subcommand a substitution prints reads as the hazard its entry names: `gh repo $(echo view) o/r` blocks under gh-repo-delete and `git $(echo status)` warns under git-clean. (2) A program name nothing fixes can be `pkill` or `killall`, whose entries name nothing but the program and an operand, so an unknown name with any operand blocks under them (`"$(which python3)" script.py`; spelling the program's name is the way past); a markdown line whose backtick span stands in command position reads the same way, which is the only change the 4,110-line false-positive sweep showed (two prose lines, no command). (3) An unknown name followed by `-c '<string>'` reads the string as a shell's, so one the guard cannot parse warns as a shell payload would. (4) More than eight substitutions where a command's program name could be, and an unknown dash-word that can be env's `-S` with a value glued on that the guard cannot read, are refused: the first because every such name costs a read of the whole line, the second because the value is not a plain command.
2026-09-25 — Correction to the entry above on the shell guard's unknown word (fix round 3 of lane guard, run A, closing review3-guard). Its mark was not unforgeable as internal/core/guard/unknown.go claimed: an ANSI-C escape that decodes to NUL ($'\x00', $'\0', $'\u0000') put the mark into a word after Check had dropped the line's own NUL bytes, and a blocked command's name behind one allowed (iss-2609252020432185). It is unforgeable by construction now: Check drops the line's NULs, and an ANSI-C string ends at its first decoded NUL, as bash ends it. An out-of-band flag on the word was weighed and not taken, because every reader in the package holds words as strings and the change would not have been contained. The same round makes the rule total: every reader of a word or a command name reads through unknown.go, an unknown dash-word is read both as a flag that stands alone and one that takes a value (and as a shell's -c or a verb's payload flag) before command position as after it (iss-2609252020507464), and a substitution in command position is every program its known tail allows, a shell, a wrapper and git among them, which resolves the command-position half of iss-2609251824244354; that record now names only the parameter-expansion half (--$X, ${…}), still deferred with its reason, so allow (4) above is that half alone. A test parses the package and fails on a reader of a word's dash or a command's name that is not listed with how it reads the rule. Four over-blocks are accepted in the fail-closed direction, each recorded so it is not mistaken for a defect. (1) An unknown operand can be any subcommand, so a read-only command whose subcommand a substitution prints reads as the hazard its entry names: gh repo $(echo view) o/r blocks under gh-repo-delete and git $(echo status) warns under git-clean. (2) A program name nothing fixes can be pkill or killall, whose entries name nothing but the program and an operand, so an unknown name with any operand blocks under them ("$(which python3)" script.py; spelling the program's name is the way past); a markdown line whose backtick span stands in command position reads the same way, which is the only change the 4,110-line false-positive sweep showed (two prose lines, no command). (3) An unknown name followed by -c '<string>' reads the string as a shell's, so one the guard cannot parse warns as a shell payload would. (4) More than eight substitutions where a command's program name could be, and an unknown dash-word that can be env's -S with a value glued on that the guard cannot read, are refused: the first because every such name costs a read of the whole line, the second because the value is not a plain command.
Frontmatter
| id | dcn-2609250000000015 |
|---|---|
| type | decision |
| date | 2026-09-25 |
.abcd/work/decisions/dcn-2609250000000015-correction-to-the-entry-above-on-the.md
open on GitHub ↗ · commit history ↗
mentions4
- iss-2609251824244354The shell guard reads a command substitution's output as an unknown word, but not a parameter expansion's. A dash glued to a variable (git push --$X origin main, rm -$F after a cd) is read as the literal text --$X, which names no flag, so every blocker allows it while its --$(echo x) twin blocks; and ${GIT:-git} standing in command position is compared as text. bash builds the hazard from either. Found while closing review2-guard (its finding 1 names --$X as pre-existing). The record named a second half, a substitution standing in command position, which fix round 3 of lane guard resolved (a name a substitution prints is every program its known tail allows); this record is the parameter-expansion half alone. Fix round 4 made a ${…} that carries a command substitution (${X:-$(…)}, --${X:-$(…)}) unknown from its ${ on (iss-2609252120211621). What remains is exactly a parameter expansion with no substitution in it: $X, ${X}, ${X:-word} and the other operators with literal words, and $@ / $*, standing as the command's program name, as a flag or glued to one, as an operand an entry constrains, or inside a payload the guard reads.
- iss-2609252020432185The shell guard's unknown-word mark could be forged from the command line: an ANSI-C escape that decodes to NUL (a hex, octal, unicode or control escape) put the tokenizer's substitution mark into a word after Check had stripped the line's own NUL bytes, so an ANSI-C NUL glued before a blocked command's name made that name an unknown word compared as text, and every blocker allowed. bash ends an ANSI-C string at its first NUL and runs the name that follows. Found by review3-guard finding 1.
- iss-2609252020507464The shell guard's readers that step option words before command position, or read a shell's -c, took an unknown dash-word (a dash glued to a command substitution) as one fixed thing: never a value flag, never -c. The wrapper walk (sudo, env, nice, timeout, exec, doas, stdbuf, xargs), the operand walk that finds a subcommand (git, gh), the exec-string scan (su -c) and the shell -c reader each read it so, and a hazard behind such a word allowed or only warned, which runs it. Found by review3-guard finding 2.
- iss-2609252120212639The shell guard reported a command whose program name is a command substitution under whichever registry entry fired first, with that entry lesson: killall-by-name telling an agent to stop a build command by its pid, where the way past the recorded over-block is to spell the program name. The over-block is also wider than the decision [dcn-2609250000000015](../../decisions/dcn-2609250000000015-correction-to-the-entry-above-on-the.md) states: any command word whose basename ends in a substitution, with any operand (review4-guard finding 4).