Development record
round-2 fixed nitpick batch: guard check help omits the disclosed backtick limit; urlguard misses CGNAT 100.64/10 and other RFC 6890 ranges; readHookInput misreports an over-cap payload as malformed (iss-201's class at the shared helper); repolint privacy-hygiene silently skips textual files over the 4 MiB cap; capture reCommitSha refuses 64-hex commits; launch Short hides that --dry-run is required; release.yml verify-tag comment overstates what gh verifies; release.yml header says dispatch runs only the rehearsal job; dependabot covers no pip ecosystem for docs; brief pointer lines enumerate five of nine internals chapters; phase-6 and 06-lint carry change-narration blocks; 05-internals cites a ledger entry that does not exist; 03-evidence names the retired activity record nitpick documentation
round-2 fixed nitpick batch: guard check help omits the disclosed backtick limit; urlguard misses CGNAT 100.64/10 and other RFC 6890 ranges; readHookInput misreports an over-cap payload as malformed (iss-201's class at the shared helper); repolint privacy-hygiene silently skips textual files over the 4 MiB cap; capture reCommitSha refuses 64-hex commits; launch Short hides that --dry-run is required; release.yml verify-tag comment overstates what gh verifies; release.yml header says dispatch runs only the rehearsal job; dependabot covers no pip ecosystem for docs; brief pointer lines enumerate five of nine internals chapters; phase-6 and 06-lint carry change-narration blocks; 05-internals cites a ledger entry that does not exist; 03-evidence names the retired activity record nitpick documentation
Evidence (per item; all verified by independent refuters)
- guard help:
internal/surface/cli/guard.go:61-76gap list omits the backtick limit thatinternal/README.md:60-62and dcn-2608010000000004 claim is stated there (the parser gap itself is iss-148, open, deliberately deferred after a reverted fix). - urlguard:
internal/urlguard/urlguard.go:32-45misses RFC 6598 100.64.0.0/10 (Tailscale/AWS-internal; verified no predicate catches it, NAT64 unwrap re-checks the same list) plus 198.18.0.0/15, 192.0.0.0/24, 240.0.0.0/4, 0.0.0.0/8; doc-comment overclaims. readHookInput(internal/surface/cli/cli.go:950) lacks the cap+1 probe, so an over-cap prompt-router payload is misreported as malformed JSON (fails closed+loud; only prompt-router is reachable — the other hook payloads are path-sized). Same class as iss-201 at the shared helper; the fix routesguard.go:169through the same probe and closes iss-201.- repolint privacy-hygiene:
rule_privacy.go:275-277returns not-ok for >4 MiB files and the caller bare-continues (:102-106) — "conforms" without scanning, against the engine contract atrepolint.go:93-94. Warn only for textual files (first-8-KiB isBinary probe) so binary assets stay quiet. capture.go:239reCommitSha {7,40}refuses a 64-hex commit; the siblinglint.go:78already accepts{7,64}; spc-25's rationale (never refuse a legitimate resolution) argues for widening. The value is a provenance stamp, not a filter.cli.go:243launch Short reads as optional while bareabcd launchrefuses; thecli.go:2099list Short is the house precedent for naming a required flag.release.yml:307-308comment claims--verify-tagchecks the tag points at the checked-out commit; gh checks existence only, asrelease.yml:22-23itself states. Same false conjunct twice inscaffold/templates/release.yml.tmpl. Comment fix only; no assertion step (tag-move is governed elsewhere; a step would imply a guarantee it cannot give).release.yml:36"runs ONLY the rehearsal job" is false (verifyhas noif:and runs on dispatch too);:453"full gate armed" overstates the two commands the job runs — align with:440's own hedge. Mirrored in the template.dependabot.ymldeclares gomod + github-actions only;docs/requirements.txthas no pip ecosystem; gitleaks/zizmor pins are hand-reviewed with no comment saying so.- brief pointer lines enumerate five of nine 05-internals chapters (
brief/README.md:26,brief/04-surfaces/README.md:54; the tree line and the 05-internals index are the single rosters to keep). - change-narration blocks:
roadmap/phases/phase-6-lifeboat.md:19-33,:79-84,:131-138, and the "formerly pending here" parenthetical atbrief/05-internals/06-lint.md:35(rule atdevelopment/README.md:25; content decision-anchored, rephrase only). brief/05-internals/README.md:21cites a ledger entry "[Brief restructure — future enforcement note]" that has never existed in any tree (structurally impossible in the iss-N store).brief/03-evidence/README.md:6-7names "the activity record", retired by adr-32; the sibling chapter already says "the working record under.abcd/work/".
Frontmatter
| id | iss-356 |
|---|---|
| type | issue |
| lifecycle | resolved |
| severity | minor |
.abcd/work/issues/resolved/iss-356-round-2-fixed-nitpick-batch.md
open on GitHub ↗ · commit history ↗
mentions9
- adr-32The issue ledger is working-tier data, not authored record
- dcn-26080100000000042026-08-01 — guard batch (iss-159/144/148), REVERT of the backtick sub-part after a THIRD adversarial round; the other three sub-parts stand. Following backtick command substitution went in, then grew a frame/stack mechanism intended to hold two properties at once — a substitution's content checked as its OWN segment (so a hazard hidden inside one is matched) while the ENCLOSING command's tokens, chain index and command position survived the boundary. Three rounds, three fresh independent reviewers, three BLOCKER findings, all in that mechanism rather than in one instance of it: (1) a substitution before trailing flags truncated the enclosing segment, so `rm` + substitution + `-rf *` went block to allow; (2) the frame fix for (1) renumbered the enclosing command's chain when a newline fell inside a substitution, defeating every `after_cd` entry (`cd scratch && rm -rf $(\nfind . -name x\n)`); and (3), the one that decided it, a substitution in LEADING command position left a bare `$` as the enclosing command's argv[0], so `commandOf` returned `"$"` and NO entry could match — that is not a hole in `rm-rf-after-cd-chain`, it is a hole in the registry, reachable by prefixing any hazard with `$(true)`. Two more in the same round: a bare `(` nested inside `$( … )` mis-popped the frame and re-opened (1) in a narrower shape, and an unterminated backtick failed OPEN. DECISION: revert rather than patch a fourth time inside one round. A mechanism that has produced a security-relevant regression on every attempt is not converging, and each fix has been strictly worse than the last in blast radius — the third defeats the entire registry. `internal/core/guard/tokenize.go` is restored byte-for-byte to `main`: no backtick handling at all (literal text), `$( … )` exactly as it was, and the four guard.json fixtures the frame mechanism added removed with it. The two claims of the entry above are therefore WITHDRAWN, not superseded: backtick substitution does NOT reach command position, and the enclosing command does NOT survive a substitution. The literal ledger ask ("a backtick command substitution is not followed, while the dollar-paren form is") asked for PARITY with `$( … )` as it behaves today, and parity is what leaving both forms untouched delivers — the gap is disclosed again as a stated v1 limit on all four coverage surfaces (brief, plugin command, CLI long help + generated reference, package map), which is the honest form of it. Recorded rather than quietly carried: `$( … )` on `main` already loses the flags written AFTER a substitution — `rm $(true) -rf *` does not read as a recursive force delete — a pre-existing gap this round's review process surfaced and did not introduce, undisclosed before it and undisclosed still, because closing it is the same design problem as following a backtick and the two should land together or not at all. iss-148 is re-scoped IN PLACE back to open on that one sub-part (the iss-96 precedent: a `capture resolve` would have to state a resolution for scope that is not settled), with the three shipped sub-parts and their commits named in the entry; iss-144 and iss-159 are untouched and stay resolved, neither being implicated in any review finding. Deferred as its own future work, and explicitly NOT a patch to the reverted code: a substitution model that follows the payload AND preserves the enclosing command across the boundary, which needs the tokenizer to carry a command-position stack rather than a segment flush — the reason three attempts to bolt it onto the flush all leaked.
- dcn-26082000000000022026-08-20 — bughunt round 2 (branch bughunt-b/round-2): captured the round-2 records (iss-346..iss-357, with the two colliding ids re-minted as iss-358/iss-359), fixed
- iss-148guard registry coverage gaps found while wiring itd-103 (registry content, not matching semantics): a push whose refspec carries a leading plus is a force in disguise and no entry describes it; xargs, timeout and exec are absent from the matcher's wrappers set, so a hazard launched through one of them is not seen; a backtick command substitution is not followed, while the dollar-paren form is; and a wrapper that IS in the set defangs an entry the moment it carries its own flags, because only the wrapper name is stepped over — `sudo <hazard>` is seen, `sudo -u bob <hazard>` is not, and the same holds for `env -i` and `time -p`. That last one is the sharpest: it turns an entry the registry does describe into an allow with one extra token, and it is the only item here that a facilitator would reasonably assume was covered. Candidates for the admission gate as the registry grows from reality; the wrapper-flag item is matcher-side (`commandOf` in internal/core/guard/match.go), not registry content.
- iss-159hazard registry has no entries for destructive GitHub remote operations: 'gh repo delete' and 'gh api -X/--method DELETE repos/{owner}/{repo}' both verdict allow. Field evidence: the user-level guard-bash.sh hook blocks both (repo deletion is human-only; second layer behind the missing delete_repo scope) — that hand-wired hook is the prior art to lift into the bundled registry defaults, same prototype-to-product path as itd-74. Registry entries should mirror the hook's nuances: quote-tolerant token matching (already native), depth-limited api paths (DELETE on deeper repo subpaths stays allowed), and the block message naming the human-only successor.
- iss-201abcd guard hook reads its PreToolUse stdin payload via a bare LimitReader with no overflow check, so a payload padded past the 1 MiB cap (e.g. a blocked command with a trailing shell comment full of junk) is silently truncated, breaks JSON parsing, and routes to the fail-open-loud path (exit 1, command runs) with a diagnostic that misleadingly blames the host ('not readable JSON') instead of reporting the actual cap overflow; the sibling check verb's guardCandidate already reads cap+1 specifically to detect and refuse this. Independently verified: threshold is sharp and deterministic (cap-1 blocks, cap+1 fails open), always a loud (not silent) fail-open, exploitation cost bounded by needing >1 MiB of attacker-influenced content inside one tool_input
- iss-392bughunt round-3 confirmed nitpick batch — zero-padded record-id filenames evade the id-uniqueness lint in all three families; the go-version lockstep test skips .yaml workflows; scripts/*.sh lack the eol=lf pin the repo's own policy states; agents/CHANGELOG announces lifeboat-reviewer 0.1.1 while the prompt carries 0.1.0; five live records still route to the pre-flattening commands/abcd/ and retired docs/reference pages
- iss-2608261041029596brief/README.md still says the internals README indexes nine chapters and its tree roster omits site, but there are ten. brief/README.md line 52 reads 'indexes the nine chapters' and the tree line 40 lists nine internals topics without site, while 05-internals/README.md indexes ten rows (10-site.md exists on disk). iss-356 designated the tree line and the internals index as the single rosters to keep in sync; a tenth chapter was added and the index updated but the count and tree line were not. No lint rule gates this roster. Fix: nine to ten, add site to the tree line.
- spc-25a-resolved-issue-points-at-what-fixed-it-abcd-capture-resolv