fix: bug-hunt round 7 — guard > bypass, frontmatter BOM bypass, and 14 more - #415
Merged
Conversation
The tokenizer read a leading `&` as a background/`&&` operator, so gluing or spacing bash's both-streams redirection into a command (`git push &>/dev/null --force origin main`) split the simple command in two and dropped the dangerous flag out of command position -- a silent allow on every blocker-tier entry. `&>` / `&>>` are now recognised as a redirection before the list-operator split; the target is dropped and, unlike `>`/`<`, a preceding digit is kept as a real argument (bash does not read it as an fd for `&>`). Token- and decision-level tests watch each blocker form flip from allow to block. Assisted-by: Claude:claude-fable-5
The frontmatter scanners anchored on line 0, and TrimSpace does not strip a UTF-8 byte-order mark, so a record led by a BOM or a multi-line `<!-- ... -->` comment yielded an empty field map and slipped the no_git_metadata blocker and the record_schema id/supersession gates entirely -- the same silent fail-open the single-line-comment fix closed. A shared frontmatter.TrimBOM strips a leading mark in Fields, and the lint and glossary scanners now skip a multi-line comment statefully before the `---`, with absolute line numbers preserved. Assisted-by: Claude:claude-fable-5
The privacy scanner read exactly maxScanBytes, so a tracked file that grew past the cap between the fstat and the read was scanned as a truncated prefix and reported clean -- a false "scanned" on a privacy control that bypasses the not-scanned warning the same file over the cap at stat time already takes. The reader now probes one byte past the cap and routes a grown file to that same not-scanned path. A boundary test pins the cap; the growth race itself is closed by construction. Assisted-by: Claude:claude-fable-5
abcd spec, abcd intent, abcd memory, and abcd capture emitted bare null for an empty collection, so a consumer iterating the value (jq `.[]`, an agent following the command doc) errored on null. The core constructors now seed the collections non-nil, so an empty store marshals an empty array -- the invariant history list already held. Assisted-by: Claude:claude-fable-5
abcd adr-N routed by a fixed four-digit filename prefix, so a differently-padded ADR file -- lint-green and citation-resolvable, since record-lint and the citation resolver compare numerically -- was reported not found by the one reader that pinned padding. Dispatch now routes by the filename's numeric ordinal, the filename-ordinal residue of the earlier frontmatter-id fix; the spc-26 spec line is amended to match. Assisted-by: Claude:claude-fable-5
external-review.yml suppresses zizmor's dangerous-triggers audit on its pull_request_target trigger, so the safety invariant it states in prose (PR code is never checked out) is unenforced: a later checkout added to that job would pass every zizmor persona -- a pwn request. A workflow-walking test now asserts that no pull_request_target workflow references actions/checkout, arming the invariant the comment only asserts. Assisted-by: Claude:claude-fable-5
The update refusal detail and receipt (target_path) carried the developer-identity home root raw into their success and --json envelopes -- which the CLI error scrub never sees -- including the documented plugin-session refusal the plugin relays into agent chat; a new fsutil.RedactHome redacts them to ~ at the point they enter the string. Separately, the dispatch switch had no default, so a target kind it could not classify fell through to fetch-and-swap; the regular-file case is now explicit and every other kind a named refusal (a mutating verb never proceeds on input it cannot classify). Assisted-by: Claude:claude-fable-5
Four surface fixes that share internal/surface/cli/cli.go: - ahoy detection tested .git for dir-ness, so a linked worktree or submodule (where .git is a gitfile) was misclassified as an unmanaged folder -- every gap detector skipped and `ahoy install` exiting 0 with a wrong "not a git repository" reason. It now tests existence, the last isDir(.git) holdout. - ahoy --json serialised a never-computed all-false guard object for an unmanaged folder, reporting a broken guard beside a resolved plugin root; the guard field is now a pointer omitted for a folder, matching the banlist sibling. - abcd history list/show/capture echoed the absolute home-rooted record path into their success and --json envelopes; the path is redacted to ~ at the render boundary (callers re-derive the file handle from disk, never this value). - abcd docs lint returned an engine or config fault as exit 1 -- the code a blocker finding uses -- while abcd lint and record-lint exit 2; the fault path now exits 2 with a path-scrubbed message. Assisted-by: Claude:claude-fable-5
- The mental-model brief said the corpus has zero live bundles while four planned intents declare a bundle-member of spc-83; reworded to "declared but not yet delivered" (no shared spec minted), the residual of iss-123's README-only fix. - sota-per-intent.md linked adr-22/adr-26 to the decisions directory rather than the ADR files; repointed. - commands/version.md now notes install_mode is omitted from the JSON when no abcd-owned PATH entry is resolvable, rather than always naming it. - AGENTS.md and CONTRIBUTING.md record that the CI classifier stands the macOS leg, race lane, zizmor, govulncheck and smoke down on a docs-only pull request (the merge-queue run still gates the merge with the full set). - docs/requirements.txt no longer claims a reproducibility its unpinned transitive dependencies do not deliver. Assisted-by: Claude:claude-fable-5
…ummary Round 7 of the all-dimensions bug hunt: 8 substantive (2 major, 6 minor) and 8 nitpick findings confirmed and fixed, 7 refuted or carried as prior art. The two majors are the guard &> redirection bypass and the frontmatter BOM/comment blocker bypass. Records captured and resolved under .abcd/work/issues/, a CHANGELOG entry per user-facing change, and the round summary appended to DECISIONS.md. Assisted-by: Claude:claude-fable-5
Deploying with
|
| Status | Name | Latest Commit | Updated (UTC) |
|---|---|---|---|
| ✅ Deployment successful! View logs |
abcd | 8e716e1 | Aug 22 2026, 12:32 PM |
Assisted-by: Claude:claude-fable-5
The Plan-refusal path built its receipt with the raw target path, so the refusal envelope (text and --json) shipped the absolute home root beside the already-redacted refusal detail — the one shape the CLI error-surface scrub never touches, and the one the plugin relays into agent chat. Construction now goes through a refusalReport seam that applies fsutil.RedactHome, pinned by a test that fails when the redaction is removed. Closes the residual the merge-gate review found in iss-2608220142158516's fix. Assisted-by: Claude:claude-fable-5
capture.List returned nilable issues/skipped slices, so an empty ledger
marshalled {"issues": null, "skipped": null} — on exactly the surface
commands/capture.md tells an agent to iterate. Seed both non-nil, matching
the Status envelope's recent_open seeding, and add the verb to the
class-wide empty-collections test (watched fail before the fix). Closes
the residual the merge-gate review found in iss-2608220147106835's fix.
Assisted-by: Claude:claude-fable-5
The cap-boundary test exercised only the fstat-time cases, which the pre-fix read also handled — reverting the cap+1 probe left the suite green, so nothing guarded iss-2608220144233519's fix. Extract the capped read into capRead and drive it with a reader carrying one byte more than the fstat-time cap: the grown file must be refused whole, not returned as a truncated prefix scanned clean. Verified to fail against a capped read without the +1. Also corrects the stale never-more-than-maxScanBytes doc comment. Assisted-by: Claude:claude-fable-5
Captures iss-2608221126066379 (frontmatter BOM tolerance now diverges from the sibling parsers whose comments promise byte-exact parity) and iss-2608221126066631 (guard process-substitution redirection family, pre-existing) as open ledger records, and appends the merge-gate remediation entry to the decision log. Assisted-by: Claude:claude-fable-5
Resolves the one conflict — update.go's import block, where round 7 added fsutil (the update-history home-path redaction) and main added term (the banner's TTY-check consolidation). Both fixes are kept; preflight and gofmt are clean on the merged tree. Assisted-by: Claude:claude-fable-5
Picks up #428 (the interview-workstream records), which landed while the previous merge was in flight. Clean auto-merge; preflight and gofmt are green. Assisted-by: Claude:claude-fable-5
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What
Round 7 of the autonomous all-dimensions bug hunt. Five parallel Opus 5 hunters swept the four dimensions; every candidate was adversarially refuted by an independent Opus 5 subagent before capture. 8 substantive (2 major, 6 minor) + 8 nitpick findings confirmed and fixed; 7 refuted or carried as prior art. Each finding is captured and resolved in
.abcd/work/issues/.Security (major):
&>bypass (iss-2608220131352917) — the tokenizer read a leading&as a background/&&operator, so a glued or spaced&>/&>>both-streams redirection (git push &>/dev/null --force origin main) split the simple command and dropped its dangerous flag out of command position — a silent allow on every blocker-tier entry. Now recognised as a redirection before the list split; target dropped, fd digit kept.iss-2608220134344680) — the scanners anchored on line 0 andTrimSpacedoes not strip a UTF-8 BOM, so a BOM- or multi-line-comment-led record yielded an empty field map and slipped theno_git_metadatablocker and therecord_schemaid/supersession gates. Sharedfrontmatter.TrimBOM+ stateful comment skip in the lint and glossary scanners.Fixed (minor):
update/historyhome-path leak (iss-2608220142158516) — absolute home-rooted paths in the update refusal detail/target_pathand history recordpath(text and--json), including the plugin-session refusal the plugin relays into agent chat; redacted to~via a newfsutil.RedactHomeat the render boundary.iss-2608220136593438) —.gittested for dir-ness misread a linked worktree/submodule as an unmanaged folder (ahoy installexited 0 aborted with a wrong reason); now tests existence, the lastisDir(.git)holdout after iss-72.iss-2608220136597127) —--jsonserialised a never-computed all-false guard object for a folder; now a pointer omitted for a folder, matching the banlist sibling.iss-2608220144233519) — a file grown past the scan cap during the read was scanned as a truncated prefix and reported clean; now cap+1 with the grown file routed to the not-scanned path.nullcollections (iss-2608220147106835) —spec/intent/memory/captureemitted barenullfor an empty collection; constructors seed them non-nil so an empty store marshals[].iss-2608220150151397) — reworded to "declared but not yet delivered" (residual of iss-123's README-only fix).Nitpicks: update fail-closed switch (
iss-2608220142154022), docs-lint engine-fault exit 2 (iss-2608220145356167), ADR filename-ordinal dispatch + spc-26 amend (iss-2608220148289898),pull_request_targetcheckout-guard test (iss-2608220149008905), and doc corrections tosota-per-intent.mdlinks (iss-2608220150157497),version.mdinstall_mode (iss-2608220150154972), the CI classifier standdown in AGENTS/CONTRIBUTING (iss-2608220150152332), anddocs/requirements.txttransitives (iss-2608220150152535).Refuted / prior art (not in the ledger): the
abcd-cliname incommands/launch.md(the live internal project name, record-lint-prescribed); thespc-3predecessor-store collision (a new instance of open iss-239, carried there); the.abcd/workroster omission (round 4's highlight-not-inventory doctrine governs); the CI gitleaks same-origin checksum (adr-46's accepted trust bar, tracked as open iss-379); the go.mod vs setup-go patch coupling (GOTOOLCHAIN=autoself-corrects); the Makefile CRLFeolpin (GNU make strips the CR on POSIX); and the wrangler/mkdocs docs-site deploy pin (dashboard-side, not an in-repo mechanism).Verification
make preflightgreen (build, vet, test,-race ./internal/..., and the three lint gates) before any change and on the final tree;gofmt -l .silent;scripts/check-attribution.sh commits origin/main HEADclean.&>block on all blocker forms, the BOM/comment blocker reach, the gitfile worktree classification, the guard-health omission, the home-path redaction, the docs-lint exit code, the fail-closed switch, the JSON[]invariant, and the ADR-ordinal dispatch. The repolint size TOCTOU is closed by construction; its boundary test guards the cap, not the race.Assisted-by: Claude:claude-fable-5