Development record
2026-08-18 — adr-42 parts B and C built; iss-272 closed. Part B named fourteen wrappers
2026-08-18 — adr-42 parts B and C built; iss-272 closed. Part B named fourteen wrappers
(nice setsid stdbuf ionice eatmydata proxychains chrt taskset unshare nsenter flock chroot
runuser busybox), which upgrades each from part A's loud warn to a precise Tier 1 block.
Every flag list is DERIVED BY PROBING the installed binary in a test, never from --help,
and the probe caught two errors before they shipped: nsenter -W/--wd consumes NOTHING
while unshare -w/--wd — same letter, same package, same version — is required-argument
(listing nsenter's would have made the walk step over the COMMAND, a miss the table would
have INVENTED); and taskset -c is a format switch for the MASK operand, not a value flag.
A flag this environment cannot classify is listed as unprobed WITH ITS REASON (setsid --ctty
needs a controlling terminal, chrt --sched-runtime needs SCHED_DEADLINE, nsenter/chroot/
runuser need privileges) — silence is what gh-299 shipped. Part C added an exec-string TABLE
for su/runuser/script/flock, not a shellCPayload generalisation, which would have resolved
the short spellings free and silently allowed six long ones (su --command, --command=,
--session-command, runuser --command, script --command, flock --command); each has a test.
Classification runs on the raw token chain before commandOf because runuser and flock are
also wrappers. Recorded limit kept honest: su -c runs the TARGET user's login shell,
overridable with -s, so the payload is not guaranteed POSIX grammar — a false-negative cost
only. All ten of iss-272's bypasses now produce a verdict; the acceptance test asserts that
in the issue's own terms.
Frontmatter
| id | dcn-2608180000000003 |
|---|---|
| type | decision |
| date | 2026-08-18 |
.abcd/work/decisions/dcn-2608180000000003-adr-42-parts-b-and-c-built-iss-272.md
open on GitHub ↗ · commit history ↗
mentions2
- adr-42The guard's parse layer is a mistake filter, not a security boundary — matching is two-tier, and completeness is abandoned as a goal
- iss-272The guard's WRAPPER set has the same incomplete-enumeration shape gh-297 fixed for interpreters. gh-297 made the execute-a-string INTERPRETER family one shared predicate (sh, bash, dash, zsh, ksh, mksh, ash). The wrappers map in internal/core/guard/match.go is a separate list of commands that merely exec another, and it omits several that are equally ordinary: nice (and 'nice -n 5'), setsid, flock, stdbuf, ionice, unshare, chroot, and busybox sh -c / busybox ash -c. Each is a silent allow today for every bundled blocker -- and equally so before gh-297 with plain sh, so none is introduced by that change. Also absent from BOTH sets: su -c '<hazard>', runuser -c '<hazard>' and script -c '<hazard>' /dev/null, which are execute-a-string interpreters in exactly the sense gh-297's rationale describes (each runs its -c operand with the grammar the tokenizer already parses) but are not shells, so they belong in neither list as currently shaped. The realistic ones are nice, setsid, busybox sh and su -c. Out of scope by contrast: ssh host '<cmd>' and docker run img sh -c '<cmd>' execute on a different host or namespace. Note for whoever takes this: gh-297's comment now says explicitly that 'nowhere left to widen' covers the interpreter set only, not the wrapper set, so the claim and this gap do not contradict each other. Found by the security review of gh-297.