Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 19 additions & 0 deletions .abcd/work/DECISIONS.md
Original file line number Diff line number Diff line change
Expand Up @@ -209,3 +209,22 @@ parallel-agent merge contention bites.
deferred to the richer spec-store slice. Reviewer roles 2/3 (itd-48),
loop-to-acceptance (itd-50), bundle/discipline lifecycles, and the spec
dependency graph are all explicitly deferred.
- 2026-07-12 — clean-slate hardening run STEP 0 triage. A fresh adversarial
sweep (15 ruthless + 9 security reviewers over current `main`, every finding
independently verified) returned 34 real findings (19 CONFIRMED, 15 PLAUSIBLE,
0 REJECTED; full corpus `.abcd/.work.local/logs/clean-slate-run/sweep-findings.json`).
Key result: the sweep INDEPENDENTLY RE-CONFIRMED the 2026-07-08 review's
code-defect backlog (iss-29/30/32/33/34) is real and still unfixed — prior runs
deferred those code fixes for docs-reconciliation (iss-35/36) and itd-80 feature
work. Draining them is this run. Two BLOCKs found: the scanner serialises a
finding's snippet masking only its own token, leaking sibling secrets on the
same line (iss-65). Triage disposition: newer-package findings (scanner, rules,
intent, spec, frontmatter, lint receipt-gate, capture concurrency, core) minted
as iss-65..72; older-package findings map to existing homes — memory ingest
(C12/C13/P11)→iss-30, atomic-write/fsutil (P1/P6/seed2)→iss-32, ahoy install
(C2/C3)→iss-33, launch glob panic (C11)→iss-34, identity fail-open (C8/P12)→iss-63,
history redaction (C6/C7/P2)→iss-29. iss-70's C16 fix adds `policy.detector` to
the receipt-JSON schema — a record-lint CONTRACT change flagged for maintainer
sign-off before landing (a STOP-adjacent design surface). Ledger triage committed
to `main` as a `chore:` record commit (matches prior record-to-main practice;
keeps the fix branches clean); each code fix lands on its own `auto/*` branch + PR.
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
---
schema_version: 1
id: "iss-65"
slug: "scanner-serialized-secret-crossleak"
severity: "critical"
category: "bug"
source: "agent-finding"
found_during: "clean-slate-sweep"
found_at: "internal/adapter/scanner/finding.go"
---

scanner serialized-finding secret cross-leak (BLOCK) + scan hygiene: Finding.MarshalJSON rebuilds the snippet from the full source line but masks only THIS finding own token, so a second secret on the same line (minified JSON, collapsed .env, a=X; b=Y) is serialized verbatim (finding.go:124). Also isText caps the UTF-8 sniff at 8192 bytes and utf8.Valid fails when the cut splits a multibyte rune, misclassifying a valid >8KB text file as binary and skipping the scan (scanner.go:338); ScanBundle drops an unreadable file with a bare continue instead of surfacing it in Unscanned (scanner.go:269); per-repo pii.json skip-lists have a severity floor but no coverage floor (scanner.go:128). Detector: two-secrets-one-line redaction test; mid-rune isText case; unreadable-file-surfaced case; skip-list coverage floor. Corpus: sweep C14/C17, C15, C18, P10.
12 changes: 12 additions & 0 deletions .abcd/work/issues/open/iss-66-rules-loader-trust-boundary.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
---
schema_version: 1
id: "iss-66"
slug: "rules-loader-trust-boundary"
severity: "major"
category: "bug"
source: "agent-finding"
found_during: "clean-slate-sweep"
found_at: "internal/core/rules/rules.go"
---

rules loader trust-boundary (itd-3): an untrusted per-repo .abcd/rules.json can silence default guardrail domains — Merge() ORs the global kill switch (rules.go:156) and mergeDomain lets an override set any default domain dormant, so a cloned repo suppresses all rule injection (P15); session-state lives in a predictable shared /tmp path (os.TempDir/abcd-rules-state, sha256(session).json, session defaults to constant default) letting a local co-tenant suppress injection fail-open (inject.go:99, P14); Load() Lstats rules.json then os.ReadFile re-resolves by name, a symlink-swap TOCTOU (rules.go:132, C19). Detector: override cannot disable a default guardrail/kill-switch; state under UserCacheDir+ownership check; open-once O_NOFOLLOW+fstat. Corpus: P15, P14, C19.
12 changes: 12 additions & 0 deletions .abcd/work/issues/open/iss-67-intent-lifecycle-fidelity-gaps.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
---
schema_version: 1
id: "iss-67"
slug: "intent-lifecycle-fidelity-gaps"
severity: "major"
category: "bug"
source: "agent-finding"
found_during: "clean-slate-sweep"
found_at: "internal/core/intent/review.go"
---

intent lifecycle fidelity gaps (itd-80): Plan accepts an Acceptance-Criteria section with no top-level bullet (hasAcceptanceCriteria only checks non-blank) so the intent plans then perpetually dead-letters at ingest (countAcceptanceCriteria==0) — Plan and ingest disagree on has-criteria (intent.go:108, C9/seed8); review ingest appends the audit below the template Empty-until placeholder instead of clearing it on first verdict (seed3); ReEmitReview documents a re-park but silently no-ops on an already-INGESTED/DEAD_LETTER receipt (review.go:188, C10/seed10); DEAD_LETTER re-ingest rewrites byte-identical content instead of a no-op (seed9). Detector: Plan and ingest share countAcceptanceCriteria; first-verdict-clears-placeholder; terminal-receipt re-emit; idempotent deadletter. Corpus: C9, seed3, C10, seed9.
12 changes: 12 additions & 0 deletions .abcd/work/issues/open/iss-68-spec-store-hardening.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
---
schema_version: 1
id: "iss-68"
slug: "spec-store-hardening"
severity: "minor"
category: "bug"
source: "agent-finding"
found_during: "clean-slate-sweep"
found_at: "internal/core/spec/store.go"
---

spec store hardening (itd-80): spec.Create writes a Summary/TODO body — decide the minimal native-spec body and make record-lint tolerant (spec.go:135, seed4); ensureDir/ensureRealDir only Lstat the leaf so a symlinked ancestor (specs/, intents/) is followed — literal guard or honest comment (seed5, spec+intent); readRepoFile Lstat then ReadFile re-resolves the path (symlink TOCTOU) and the size cap is not atomic — open-once O_NOFOLLOW+fstat+LimitReader (store.go:225, P7); Close Lstat-exists-then-Rename is racy and os.Rename silently overwrites — no-replace primitive (store.go:212, P8); maxIntentSpecNum silently drops a non-null unparseable spec_id, fail-open reservation scan (store.go:146, P5). Corpus: seed4, seed5, P7, P8, P5.
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
---
schema_version: 1
id: "iss-69"
slug: "frontmatter-canonical-consolidation"
severity: "minor"
category: "tech-debt"
source: "agent-finding"
found_during: "clean-slate-sweep"
found_at: "internal/core/frontmatter/frontmatter.go"
---

frontmatter canonical consolidation + delimiter bug: internal/core/lint (frontmatterFields) and internal/core/memory (parseFrontmatter/splitFileFrontmatter) each carry their own frontmatter scanner instead of internal/core/frontmatter — migrate both, behaviour-preserving, separate refactor commit (seed1); note memory needs nested-map values (source:) the flat scanner lacks — widen or scope. Bug: the canonical scanner closing-delimiter check compares the CR-trimmed line to exactly triple-dash, so a closing triple-dash with a trailing space/tab is not recognised and body lines leak as fields — TrimRight before both delimiter checks (frontmatter.go:37, C5). Corpus: the two lint/memory copies + the delimiter case.
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
---
schema_version: 1
id: "iss-70"
slug: "lint-receipt-gate-detector-binding"
severity: "major"
category: "bug"
source: "agent-finding"
found_during: "clean-slate-sweep"
found_at: "internal/core/lint/lint.go"
---

record-lint receipt-gate hardening (VSA gate): checkReceiptGate does not bind a receipt to the gate it attests — one genuine PROMOTE receipt satisfies EVERY required gate by copying it to each path (lint.go:601, C16); ArmReceiptGate keeps committer-editable required_gates when the caller supplies an empty list, fail-open arming (config.go:136, P9); workflowStepNames captures nested name keys (with: name:) as gate step names (lint.go:857, P4). NOTE C16 adds policy.detector to the receipt JSON schema — a record-lint CONTRACT change, surface for maintainer sign-off before landing. Detector: mismatched policy.detector BLOCKED per gate; empty arming fails closed; only step-indent name captured. Corpus: C16, P9, P4.
12 changes: 12 additions & 0 deletions .abcd/work/issues/open/iss-71-capture-concurrency-and-forceid.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
---
schema_version: 1
id: "iss-71"
slug: "capture-concurrency-and-forceid"
severity: "major"
category: "bug"
source: "agent-finding"
found_during: "clean-slate-sweep"
found_at: "internal/core/capture/workflow.go"
---

capture ledger concurrency + unvalidated ForceID: transition() (resolve/wontfix) takes no flock (only reservePath does) so two concurrent conflicting transitions on the same iss split-brain it across two status dirs — permanent corruption, findIssue then always ErrDuplicateIssueID (workflow.go:129, C4); reservePath builds a path from and O_EXCL-creates a placeholder for ForceID BEFORE validateStrict checks it against the iss-id regex, a pre-validation filesystem touch/traversal (alloc.go:91, P13). Detector: hold the ledger flock across findIssue+commitTransition; reject a non-matching ForceID before any filesystem op. Corpus: C4, P13.
12 changes: 12 additions & 0 deletions .abcd/work/issues/open/iss-72-core-isgitrepo-misses-worktrees.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
---
schema_version: 1
id: "iss-72"
slug: "core-isgitrepo-misses-worktrees"
severity: "nitpick"
category: "bug"
source: "agent-finding"
found_during: "clean-slate-sweep"
found_at: "internal/core/core.go"
---

core.Status.IsGitRepo=false in a linked git worktree or submodule: isDir(.git) requires a directory but .git is a regular gitfile (gitlink) in worktrees/submodules, so a genuine checkout reports not-a-git-repo (core.go:47, C1). Detector: an exists-not-isDir check for .git with a worktree fixture. Corpus: C1.
Loading