Development record
The 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).
issueresolvedminoriss-2609252120212639
The 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 states: any command word whose basename ends in a substitution, with any operand (review4-guard finding 4).
Grounds
- pursued: no refusal on an unknown program name carries an unrelated entry's lesson; a block on such a name whose why or successor is a registry entry's would show it wrong
Frontmatter
| id | iss-2609252120212639 |
|---|---|
| type | issue |
| lifecycle | resolved |
| severity | minor |
.abcd/work/issues/resolved/iss-2609252120212639-the-shell-guard-reported-an-unknown.md
open on GitHub ↗ · commit history ↗
mentions1
- dcn-26092500000000152026-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.