From 4b776c15b6112c08ca2730d20c66552b32b9e09e Mon Sep 17 00:00:00 2001 From: REPPL <77722411+REPPL@users.noreply.github.com> Date: Sun, 12 Jul 2026 08:26:53 +0100 Subject: [PATCH] chore: triage clean-slate hardening backlog (iss-65..72) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Fresh adversarial sweep (15 ruthless + 9 security reviewers over current main, every finding independently verified) surfaced 34 real findings — 19 confirmed, 15 plausible, 0 rejected. The sweep independently re-confirmed the 2026-07-08 code-defect backlog (iss-29/30/32/33/34) is real and unfixed; draining it is this run. Newer-package findings captured as iss-65..72 (scanner secret-leak BLOCK, rules-loader trust boundary, intent/spec/frontmatter/lint/capture/core). Older-package findings mapped to existing issue homes (see DECISIONS.md). Assisted-by: Claude:claude-opus-4-8 --- .abcd/work/DECISIONS.md | 19 +++++++++++++++++++ ...-65-scanner-serialized-secret-crossleak.md | 12 ++++++++++++ .../iss-66-rules-loader-trust-boundary.md | 12 ++++++++++++ .../iss-67-intent-lifecycle-fidelity-gaps.md | 12 ++++++++++++ .../open/iss-68-spec-store-hardening.md | 12 ++++++++++++ ...-69-frontmatter-canonical-consolidation.md | 12 ++++++++++++ ...s-70-lint-receipt-gate-detector-binding.md | 12 ++++++++++++ .../iss-71-capture-concurrency-and-forceid.md | 12 ++++++++++++ .../iss-72-core-isgitrepo-misses-worktrees.md | 12 ++++++++++++ 9 files changed, 115 insertions(+) create mode 100644 .abcd/work/issues/open/iss-65-scanner-serialized-secret-crossleak.md create mode 100644 .abcd/work/issues/open/iss-66-rules-loader-trust-boundary.md create mode 100644 .abcd/work/issues/open/iss-67-intent-lifecycle-fidelity-gaps.md create mode 100644 .abcd/work/issues/open/iss-68-spec-store-hardening.md create mode 100644 .abcd/work/issues/open/iss-69-frontmatter-canonical-consolidation.md create mode 100644 .abcd/work/issues/open/iss-70-lint-receipt-gate-detector-binding.md create mode 100644 .abcd/work/issues/open/iss-71-capture-concurrency-and-forceid.md create mode 100644 .abcd/work/issues/open/iss-72-core-isgitrepo-misses-worktrees.md diff --git a/.abcd/work/DECISIONS.md b/.abcd/work/DECISIONS.md index 54786ce4..ff3eb279 100644 --- a/.abcd/work/DECISIONS.md +++ b/.abcd/work/DECISIONS.md @@ -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. diff --git a/.abcd/work/issues/open/iss-65-scanner-serialized-secret-crossleak.md b/.abcd/work/issues/open/iss-65-scanner-serialized-secret-crossleak.md new file mode 100644 index 00000000..8240a1f7 --- /dev/null +++ b/.abcd/work/issues/open/iss-65-scanner-serialized-secret-crossleak.md @@ -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. \ No newline at end of file diff --git a/.abcd/work/issues/open/iss-66-rules-loader-trust-boundary.md b/.abcd/work/issues/open/iss-66-rules-loader-trust-boundary.md new file mode 100644 index 00000000..e7987206 --- /dev/null +++ b/.abcd/work/issues/open/iss-66-rules-loader-trust-boundary.md @@ -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. \ No newline at end of file diff --git a/.abcd/work/issues/open/iss-67-intent-lifecycle-fidelity-gaps.md b/.abcd/work/issues/open/iss-67-intent-lifecycle-fidelity-gaps.md new file mode 100644 index 00000000..85cf5a53 --- /dev/null +++ b/.abcd/work/issues/open/iss-67-intent-lifecycle-fidelity-gaps.md @@ -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. \ No newline at end of file diff --git a/.abcd/work/issues/open/iss-68-spec-store-hardening.md b/.abcd/work/issues/open/iss-68-spec-store-hardening.md new file mode 100644 index 00000000..7772c7f0 --- /dev/null +++ b/.abcd/work/issues/open/iss-68-spec-store-hardening.md @@ -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. \ No newline at end of file diff --git a/.abcd/work/issues/open/iss-69-frontmatter-canonical-consolidation.md b/.abcd/work/issues/open/iss-69-frontmatter-canonical-consolidation.md new file mode 100644 index 00000000..c55d74ff --- /dev/null +++ b/.abcd/work/issues/open/iss-69-frontmatter-canonical-consolidation.md @@ -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. \ No newline at end of file diff --git a/.abcd/work/issues/open/iss-70-lint-receipt-gate-detector-binding.md b/.abcd/work/issues/open/iss-70-lint-receipt-gate-detector-binding.md new file mode 100644 index 00000000..c247cc79 --- /dev/null +++ b/.abcd/work/issues/open/iss-70-lint-receipt-gate-detector-binding.md @@ -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. \ No newline at end of file diff --git a/.abcd/work/issues/open/iss-71-capture-concurrency-and-forceid.md b/.abcd/work/issues/open/iss-71-capture-concurrency-and-forceid.md new file mode 100644 index 00000000..5f563969 --- /dev/null +++ b/.abcd/work/issues/open/iss-71-capture-concurrency-and-forceid.md @@ -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. \ No newline at end of file diff --git a/.abcd/work/issues/open/iss-72-core-isgitrepo-misses-worktrees.md b/.abcd/work/issues/open/iss-72-core-isgitrepo-misses-worktrees.md new file mode 100644 index 00000000..c9b64a86 --- /dev/null +++ b/.abcd/work/issues/open/iss-72-core-isgitrepo-misses-worktrees.md @@ -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. \ No newline at end of file