Development record
abcd guard silently allows a blocker whose exec-string verb carries a value-taking short-flag cluster before the payload flag (script -Tc out.txt -c '<blocker>'): clusteredPayload accepts the cluster without consulting the verb's value flags, mis-reads -Tc as -T -c, resolves a bogus payload and switches the Tier-2 fail-safe off for the segment
abcd guard silently allows a blocker whose exec-string verb carries a value-taking short-flag cluster before the payload flag (script -Tc out.txt -c '<blocker>'): clusteredPayload accepts the cluster without consulting the verb's value flags, mis-reads -Tc as -T -c, resolves a bogus payload and switches the Tier-2 fail-safe off for the segment
Evidence
internal/core/guard/execstring.go—clusteredPayload(tok, payloadFlags)accepts a short cluster when every letter before the payload letter passesisShortOptionLetter([A-Za-z0-9]), never consultingexecStringOtherValueFlags(which listsscript's value flags-o -O -B -m -I -T). For-Tcit treats-Tas a boolean and returns a glued/next-token payload, short-circuitingscanExecStringpast the real later-c.internal/core/guard/speculate.go— because the segment now reportscarriesPayload, the Tier-2 speculative fail-safe skips it, so neither tier produces a verdict.- Reproduced against a build of
./cmd/abcd(stdin form):script -c '…' out.txtblocks (exit 1);script -Tc out.txt -c 'git push --force origin main'allows (exit 0);su -lc '…'still blocks.script -Tc out.txt -c '/bin/echo M'printsM, so the payload genuinely runs — a true-positive miss.
Adversarial review
CONFIRMED (substantive, modest end) by an independent refuter: the -O/flock -w example
spellings are false alarms (the tool itself rejects them), but script -Tc is a working
silent-allow bypass; distinct from the filed tokenizer/wrapper gaps and from iss-272's
long-spelling table. Fix: thread the verb's value flags into clusteredPayload and decline
the cluster when a pre-payload letter is a value-taking short flag.
Frontmatter
| id | iss-291 |
|---|---|
| type | issue |
| lifecycle | resolved |
| severity | minor |
.abcd/work/issues/resolved/iss-291-abcd-guard-silently-allows-a-blocker.md
open on GitHub ↗ · commit history ↗
mentions3
- dcn-26081900000000032026-08-19 — bughunt round 1 (branch bughunt-b/round-1): captured iss-291..iss-304 and fixed
- 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.
- iss-330Parallel-mint id collision observed live: the maintainer's bughunt session and the release session each minted iss-291..293 concurrently; the collision surfaced only at the merge-queue's record-lint on the second branch, costing a full release re-cut. Direct field evidence for itd-114 (collision-proof record ids) — the ledger's next-free-integer mint is unsafe under exactly the parallel-agent workflows the repo now runs daily