diff --git a/.abcd/development/intents/drafts/itd-74-name-banlist.md b/.abcd/development/intents/drafts/itd-74-name-banlist.md deleted file mode 100644 index a511680b..00000000 --- a/.abcd/development/intents/drafts/itd-74-name-banlist.md +++ /dev/null @@ -1,40 +0,0 @@ ---- -id: itd-74 -slug: name-banlist -spec_id: null -kind: standalone -suggested_kind: null -reclassification_history: [] -severity: minor ---- - -# abcd Keeps the Names You Ban Out of Everything You Publish - -## Press Release - -> **Name a thing once as off-limits; abcd keeps it out of every published surface — and keeps the truly private ones off the machine's commits entirely.** A repo abcd configures often must not name certain things in what it publishes: a specific agent harness (so the surface stays host-agnostic), a partner's product, or — most sensitively — a *private* project whose very name is confidential. abcd manages this as a two-layer banlist. The **public banlist** is enforced deterministically in CI: named tokens in user-facing content (README, `docs/`, the shipped artefact) fail the build, with a per-line escape hatch for the rare deliberate mention. The **private banlist** is enforced by a **local, untracked guard** — because a name that must never appear *anywhere public* cannot be written into public CI config to ban it there. Its patterns live only on the developer's machine; a pre-commit guard refuses to stage any content that matches, so the string never enters tracked history in the first place. -> -> "The names I can't afford to leak are exactly the ones I can't put in a public linter rule," said Kira, a maintainer. "abcd solved that by splitting it: public names get a CI gate, private names get a local guard whose list never leaves my machine. I stopped worrying that a stray paste would ship a name I'd promised to keep quiet." - -## Why This Matters - -Two failure modes share one root. First, a tool that claims to be *host-agnostic* undermines itself the moment its published docs name a specific harness — the naming dates the content and couples the surface. Second, and worse, a private collaborator's or project's name leaking into a public repo is a confidentiality breach that a history rewrite alone cannot fully undo (merged-PR diffs and cached views persist server-side). Both are cheap to prevent and expensive to remediate. The lesson learned the hard way on abcd's own repo: **prevent at authoring time, and never let the sensitive string reach a public artefact — including the linter config meant to catch it.** - -The design tension is the interesting part: a deterministic CI gate is the right tool for *public* banned names, but it is the *wrong* place for a *private* one, because the rule would have to contain the very string it forbids. Splitting enforcement by sensitivity — public names in CI, private names in a local untracked guard — resolves it without compromise. - -## What It Looks Like - -- **`abcd` manages both layers as first-class config.** A public banlist (patterns + per-token severity + the allow-context escape) compiles into the deterministic docs-currency lint family; a private banlist is scaffolded as an untracked, per-machine file plus a committed guard hook that reads it. The literal private strings never enter tracked content or CI config. -- **Wired into install.** `abcd ahoy` scaffolds both surfaces for any repo it configures: the public family in the docs-lint config, the guard hook in the repo's committed hooks, and a gitignored banlist stub with instructions. A repo becomes name-safe by being abcd-managed, not by hand-rolling hooks. -- **Verbs to maintain it.** Add, list, and remove banned patterns; the public ones are visible and reviewable, the private ones addressed by reference (never printed into a shared artefact). -- **Reports what it cannot enforce.** The private guard is local by construction, so abcd states plainly that CI cannot enforce it — the guard protects the machine that opted in, and the public flip is gated on a from-scratch name scan. - -## Dogfood (already running on this repo) - -The concrete prototype this intent generalises is live in abcd-cli itself: a `harness/*` banned-token family in the docs-currency lint (public agent-harness names, blocker, README + `docs/` scanned), a committed `.githooks/pre-commit` guard that reads an untracked banlist for the private name, and the gitignored banlist that holds it. The feature is to lift that from a hand-wired arrangement into an abcd capability every managed repo inherits. Relates to the host-agnostic documentation principle and to the install surface (`ahoy`). - -## Open Questions - -- Config shape: one banlist file with a public/private split, or two files with different visibility and tooling? -- Whether the public family belongs in the docs-lint config verbatim or in a dedicated `names` rule with richer reporting. -- How `ahoy` seeds the private banlist without ever suggesting example private strings that could themselves leak. diff --git a/.abcd/development/intents/drafts/itd-76-source-provenance-ledger.md b/.abcd/development/intents/drafts/itd-76-source-provenance-ledger.md index 838efcd9..90313f62 100644 --- a/.abcd/development/intents/drafts/itd-76-source-provenance-ledger.md +++ b/.abcd/development/intents/drafts/itd-76-source-provenance-ledger.md @@ -22,7 +22,7 @@ builds_on: [itd-77] Automatic citation is a virtue that becomes a breach the moment a source is confidential: an agent that helpfully names "the working paper this design follows" in a commit message has leaked something no history rewrite fully recalls. The naive fix — keep the material away from the agent — throws away exactly the context that makes its design work good. The resolution is to split *consultation* from *citation* and put a durable, machine-readable record between them: influence is captured eagerly and automatically (cheap, local, append-only), citation happens lazily and manually (when permission exists). The ledger is also the seed of something bigger: a paper whose claims trace to decisions and whose decisions trace to sources is *reconstructable* rather than rewritten from memory. And because a repo is a team surface even when every corpus is personal, the public slice of the bibliography must flow through the repo — citation data is shareable; documents and influence trails are not. -This composes three existing abcd designs rather than inventing new machinery: the two-layer name banlist ([itd-74](itd-74-name-banlist.md)) supplies the leak guard; the append-only audit chain ([itd-16](itd-16-hash-chain-merkle-audit.md)) is a possible later integrity backend for the ledger — the corpus repo's git history carries tamper-evidence until then, and nothing here depends on itd-16 shipping; and the provenance substrate ([`09-provenance-substrate.md`](../../brief/05-internals/09-provenance-substrate.md)) already defines citation blocks, a source registry, and an NDA-aware publish gate for *ingested* content — this intent extends the same stance to *consulted* content. +This composes three existing abcd designs rather than inventing new machinery: the two-layer name banlist ([itd-74](../planned/itd-74-name-banlist.md)) supplies the leak guard; the append-only audit chain ([itd-16](itd-16-hash-chain-merkle-audit.md)) is a possible later integrity backend for the ledger — the corpus repo's git history carries tamper-evidence until then, and nothing here depends on itd-16 shipping; and the provenance substrate ([`09-provenance-substrate.md`](../../brief/05-internals/09-provenance-substrate.md)) already defines citation blocks, a source registry, and an NDA-aware publish gate for *ingested* content — this intent extends the same stance to *consulted* content. ## What It Looks Like diff --git a/.abcd/development/intents/planned/itd-74-name-banlist.md b/.abcd/development/intents/planned/itd-74-name-banlist.md new file mode 100644 index 00000000..d1df1555 --- /dev/null +++ b/.abcd/development/intents/planned/itd-74-name-banlist.md @@ -0,0 +1,58 @@ +--- +id: itd-74 +slug: name-banlist +spec_id: spc-20 +kind: standalone +suggested_kind: null +reclassification_history: [] +severity: minor +impact: additive +--- + +# abcd Keeps the Names You Ban Out of Everything You Publish + +## Press Release + +> **Name a thing once as off-limits; abcd keeps it out of every published surface — and keeps the truly private ones off the machine's commits entirely.** A repo abcd configures often must not name certain things in what it publishes: a specific agent harness (so the surface stays host-agnostic), a partner's product, a *private* project whose very name is confidential, or — most sensitively — the user's own machine identifiers: hostnames, device names, and the IPs or address prefixes of their private network. abcd manages this as a two-layer banlist. The **public banlist** is enforced deterministically in CI: named tokens in user-facing content (README, `docs/`, the shipped artefact) fail the build, with a per-line escape hatch for the rare deliberate mention. The **private banlist** is enforced by a **local, untracked guard** — because a name that must never appear *anywhere public* cannot be written into public CI config to ban it there. Its patterns live only on the developer's machine; a pre-commit guard refuses to stage any content that matches, so the string never enters tracked history in the first place. +> +> "The names I can't afford to leak are exactly the ones I can't put in a public linter rule," said Kira, a maintainer. "abcd solved that by splitting it: public names get a CI gate, private names get a local guard whose list never leaves my machine. I stopped worrying that a stray paste would ship a name I'd promised to keep quiet." + +## Why This Matters + +Two failure modes share one root. First, a tool that claims to be *host-agnostic* undermines itself the moment its published docs name a specific harness — the naming dates the content and couples the surface. Second, and worse, a private collaborator's or project's name leaking into a public repo is a confidentiality breach that a history rewrite alone cannot fully undo (merged-PR diffs and cached views persist server-side). Both are cheap to prevent and expensive to remediate. The lesson learned the hard way on abcd's own repo: **prevent at authoring time, and never let the sensitive string reach a public artefact — including the linter config meant to catch it.** + +The design tension is the interesting part: a deterministic CI gate is the right tool for *public* banned names, but it is the *wrong* place for a *private* one, because the rule would have to contain the very string it forbids. Splitting enforcement by sensitivity — public names in CI, private names in a local untracked guard — resolves it without compromise. + +## What It Looks Like + +- **`abcd` manages both layers as first-class config.** A public banlist (patterns + per-token severity + the allow-context escape) compiles into the deterministic docs-currency lint family; a private banlist is scaffolded as an untracked, per-machine file plus a committed guard hook that reads it. The literal private strings never enter tracked content or CI config. +- **Wired into install.** `abcd ahoy` scaffolds both surfaces for any repo it configures: the public family in the docs-lint config, the guard hook in the repo's committed hooks, and a gitignored banlist stub with instructions. A repo becomes name-safe by being abcd-managed, not by hand-rolling hooks. +- **Verbs to maintain it.** Add, list, and remove banned patterns; the public ones are visible and reviewable, the private ones addressed by reference (never printed into a shared artefact). +- **Reports what it cannot enforce.** The private guard is local by construction, so abcd states plainly that CI cannot enforce it — the guard protects the machine that opted in, and the public flip is gated on a from-scratch name scan. + +## Acceptance Criteria + +> _BDD format, per the itd-1 discipline. Confirmed by the maintainer in the 2026-07-29 planning interview._ + +- **Given** a repo whose public banlist bans token X, **when** docs-lint runs over user-facing content (README, `docs/`, the shipped artefact) containing X without the allow escape, **then** a blocker finding names the file and line; **given** the line carries the escape, **then** no finding is raised. +- **Given** a private banlist entry with key K and pattern P, **when** a commit stages content matching P, **then** the commit is refused and the refusal message names only K — the matched string and the pattern value are never echoed to any output. +- **Given** a private banlist containing a hostname, an IP or CIDR prefix, or a device name, **when** staged content matches one, **then** the commit is refused exactly as for a name entry — machine identifiers are first-class private entries. +- **Given** a fresh clone where the private banlist file does not exist, **when** the guard hook runs, **then** it prints a loud warning that the private layer is inactive on this machine and does not block the commit — never a silent pass that looks like protection. +- **Given** a repo configured by `abcd ahoy`, **when** scaffolding completes, **then** the public family is present in the docs-lint config, the guard hook is committed, the private banlist stub exists and is gitignored, and the stub's seeded examples use only reserved documentation values (RFC 5737/3849/2606/7042 ranges, persona-derived hostnames) per the `examples-use-reserved-identifiers` principle. +- **Given** the banlist maintenance verbs (add, list, remove), **when** entries are rendered, **then** public entries render in full and private entries render by key only. +- **Given** any status or report surface describing the private layer, **when** it renders, **then** it states plainly that CI cannot enforce the private list — it protects only machines that have opted in. + +## Resolved Questions (2026-07-29 planning interview) + +- **Config shape: two files.** The public banlist is committed beside the docs-lint config; the private banlist is a separate gitignored file with its own tooling. A single split file makes the whole file's visibility load-bearing — accidental commit of the private half is the exact failure mode this intent exists to prevent. +- **Public-layer home: generalise the docs-lint family.** The existing `harness/*` banned-token family is lifted into a general banned-names family — one canonical primitive, ratchet not big-bang. A dedicated rule kind with richer reporting is future work, not this intent. +- **Private-stub seeding: reserved documentation values only.** The `examples-use-reserved-identifiers` principle answers the seeding question — the stub's examples come from RFC-reserved ranges and persona-derived hostnames, so the scaffold can never suggest a plausible real value. +- **Scope: machine identifiers included.** Hostnames, device names, and IP/CIDR entries are first-class private-banlist entries (evidence: iss-158, the 2026-07-29 managed-repo NEXT.md privacy-leak investigation). + +## Dogfood (already running on this repo) + +The concrete prototype this intent generalises is live in abcd-cli itself: a `harness/*` banned-token family in the docs-currency lint (public agent-harness names, blocker, README + `docs/` scanned), a committed `.githooks/pre-commit` guard that reads an untracked banlist for the private name, and the gitignored banlist that holds it. The feature is to lift that from a hand-wired arrangement into an abcd capability every managed repo inherits. Relates to the host-agnostic documentation principle and to the install surface (`ahoy`). + +## Open Questions + +_None — all three original questions resolved in the 2026-07-29 planning interview (see Resolved Questions)._ diff --git a/.abcd/development/plans/2026-07-29-v0.5.0-security-and-consistency.md b/.abcd/development/plans/2026-07-29-v0.5.0-security-and-consistency.md new file mode 100644 index 00000000..2d849b78 --- /dev/null +++ b/.abcd/development/plans/2026-07-29-v0.5.0-security-and-consistency.md @@ -0,0 +1,147 @@ +# v0.5.0 "security & consistency" — release plan and run queue (2026-07-29) + +**Status:** the backlog for the next implementation cycle, consumed by the +generic protocol at +[`2026-07-12-abcd-run-protocol.md`](2026-07-12-abcd-run-protocol.md). This +file supersedes the pick-up role of +[`2026-07-24-next-run-queue.md`](2026-07-24-next-run-queue.md), which is +drained except for iss-38, iss-96 and iss-125 (absorbed below) and iss-105 +and iss-124 (explicitly out, listed at the end). + +**Release framing (maintainer, 2026-07-29):** one release with two halves. +The **security** half closes the leak class exposed by the 2026-07-29 +managed-repo NEXT.md privacy-leak investigation, end to end: detection +(scanner/audit/redaction), placement (tier rule), prevention (banlist, +guard). The **consistency** half retires the record/doc-currency majors so +the shipped record matches the shipped surface. The version is **derived, +never declared**: `abcd launch ship` derives it from the shipped impacts — +with itd-74 (`impact: additive`) in the cut and nothing breaking, derivation +from v0.4.1 yields v0.5.0; "0.5.0" below is that prediction, not an override. + +## Run contract + +- `backlog:` milestones — the workstreams below; each numbered item's stable + id is its issue/intent id. Issue bodies in `.abcd/work/issues/open/` are + the item spec; this file adds only ordering and collision notes. +- `gate:` `make preflight` (sole deterministic authority); record edits also + pass `go run ./cmd/record-lint` (0 blockers). +- `budget:` one item per burst; stop cleanly at the first bound reached. +- `commit_trailer:` `Assisted-by: Claude:` (kernel format; never + Co-Authored-By for AI). +- `reviewers:` correctness `abcd:ruthless-reviewer`; security + `abcd:security-reviewer` on trust-boundary diffs — every Workstream A and + B item is a trust-boundary diff by definition. +- `strike_limit:` 3 (failed + died entries per item). +- One PR per item; never commit to main directly. **Auto-merge is + authorised by the maintainer (2026-07-29) for this cycle's rounds, but + only through the merge gate**: all CI checks green, plus two independent + adversarial reviews of the full diff (correctness always; security lens + on Workstream A/B items, record/docs-accuracy lens on C/D) each ending in + an explicit MERGE verdict. A BLOCK from either reviewer, or CI that + cannot be made green, leaves the PR open for the human. Fixing a ledger + item includes moving it open → resolved (`abcd capture resolve`) in the + same PR. + +## Workstream A — close the leak class (security) + +1. **itd-74 / spc-20 `name-banlist`** — planned, specced, gate-READY + (verified 2026-07-29). Two-layer banlist; machine identifiers + first-class; `ahoy`-wired. The spec is the design record. +2. **iss-154 + iss-157 + iss-125 + iss-153 — ONE work item.** The + network-identifier detector, built once in the shared scanner pattern set + (`internal/adapter/scanner/patterns.go`) and consumed by the audit + privacy-hygiene rule and Stage-1 redaction. Allowlist inversion per the + `examples-use-reserved-identifiers` principle: flag identifiers OUTSIDE + the reserved documentation ranges (RFC 5737/3849/2606/7042; loopback and + unspecified exempt). Includes hostname shapes and LAN suffixes + (iss-125's live specimen) and the `/Users/Shared` false-positive fix + (iss-153, same regex). Shipping the lint promotes the principle to a + discipline (itd-79 path). Detector-first: fixtures watched failing + before patterns widen. +3. **iss-155** — three-tier rule flags local-tier artefacts (NEXT.md, + `scratch/`, `logs/`) present in committed tiers. +4. **iss-156** — PII rules domain: network-context recall keywords plus the + missing never-commit-hostnames/IPs rule text. +5. **iss-96 — verification milestone, not implementation.** Re-check the + transcript-scanner coverage gaps it tracks against item 2's landed + pattern set; close it or re-scope the residue. Never close on assumption. + +## Workstream B — guard and hazard-registry hardening (security) + +6. **iss-159 + iss-144 + iss-148 — ONE batch.** Registry content: + destructive `gh` remote operations (`gh repo delete`, `gh api -X DELETE` + on `/repos/{owner}/{repo}`, depth-limited), the cd-chain + cwd-relative + `rm` glob shape, the `+refspec` force-in-disguise, missing wrappers + (`xargs`, `timeout`, `exec`), backtick substitution. Matcher-side (not + registry): the wrapper-with-flags defang (`sudo -u bob ` unseen) + in `internal/core/guard/match.go`. Every entry lands fixture-first: the + block is watched firing on the exact incident shape. + +## Workstream C — boundary bug fixes + +7. **iss-30 `memory-ingest-boundary`** — HTTP status unchecked, tilde + mangling, partial-failure misreporting, CRLF parser disagreement, plus + the untested ingest paths. External-input boundary: security review + applies. +8. **iss-34 `untested-refusal-guards`** — the five named refusal paths gain + tests presenting the forbidden input and asserting rejection, error + shape, and absence of side effects (`guards-prove-themselves`). + +## Workstream D — consistency (record and doc currency) + +Added to this release by the maintainer, 2026-07-29. Issue bodies are the +item specs; no ordering constraints among them. + +9. **iss-37** `phantom-enforcement-claims` +10. **iss-38** `hand-maintained-index-drift` +11. **iss-39** `record-schema-validation` +12. **iss-40** `glossary-unification` +13. **iss-41** `lifecycle-delivery-state` +14. **iss-42** `record-orientation-currency` +15. **iss-43** `readme-capability-currency` +16. **iss-44** `plugin-surface-parity` +17. **iss-80** `record-id-allocators-branch-local` — the id-collision bug + under parallel sessions; consistency of the record store itself. + +## Ordering and collisions + +- A2 (iss-154/157/125/153) is atomic — all four touch the same pattern + set/regex; splitting them recreates the collision it avoids. +- A2 lands before or alongside A1 (itd-74): both touch the scanner/lint + seam. B, C, D are independent of A and of each other; safe in parallel. +- A5 (iss-96) runs only after A2 merges. + +## STOP conditions (this cycle) + +Hitting one means stop and report, never push through (per the playbook): + +1. **Design ambiguity beyond the recorded resolutions.** A1's design is + spc-20 plus the itd-74 Resolved Questions; A2's is the allowlist + inversion recorded in iss-154. An interpretation those records do not + settle is a STOP — do not invent the bar. +2. **False-positive storm on A2.** If the inverted detector flags more than + 20 legitimate committed identifiers in this repo, STOP and report the + distribution; mass-waiving is forbidden. +3. **Release-gate or receipt-contract changes.** No D item authorises + touching the receipt_gate contract or release workflows; any such edit + is a STOP. +4. **Missing or ambiguous record.** An issue body or spec that does not + support the work as scoped: fail closed, never synthesise a substitute. +5. **Security-review BLOCK** verdict stops the change (standing rule). + +## Release mechanics (the cut, after the backlog drains) + +Per the release-gate README (read it first, every time): one-pass doc sweep, +then the semantic gates — `docs-currency-lint` (deterministic) and +`abcd:docs-currency-reviewer` (semantic) — then the two-commit receipts +(content commit, then sha-keyed PROMOTE receipts commit; crosscheck never +from cache after brief edits), then `abcd launch ship` derives the version +and composes the changelog from the shipped records. + +## Explicitly out of v0.5.0 + +Still open, deliberately not in this cut — pull one in only by editing this +file: iss-33 (ahoy verb hygiene), iss-56, iss-62, iss-87, iss-92, iss-93, +iss-95, iss-105, iss-124 (foreign-repo review receipts — the largest +deferred major), iss-126, and all remaining minor/nitpick ledger entries not +named above. diff --git a/.abcd/development/principles/examples-use-reserved-identifiers.md b/.abcd/development/principles/examples-use-reserved-identifiers.md new file mode 100644 index 00000000..599d2b1b --- /dev/null +++ b/.abcd/development/principles/examples-use-reserved-identifiers.md @@ -0,0 +1,49 @@ +# Examples use reserved identifiers + +**The rule.** Every illustrative machine identifier in anything committed or +published — docs, tests, fixtures, intent scenarios, rule text, error-message +examples — comes from a reserved documentation range, never from a real +environment. The persona rule (itd-79) applied to infrastructure: just as a +quoted persona is always a registry name, an example host is always a fixture +host. + +- **IPv4:** RFC 5737 documentation blocks — `192.0.2.0/24`, `198.51.100.0/24`, + `203.0.113.0/24`. +- **IPv6:** RFC 3849 — `2001:db8::/32`. +- **Domains:** RFC 2606/6761 — `example.com`, `example.org`, `.example`, + `.test`, `.invalid`. +- **MAC addresses:** RFC 7042 — `00:00:5E:00:53:00`–`FF`. +- **Hostnames / device names:** derive from the persona registry + (`alice-laptop`, `bob-desktop`, `carol-server`) — role picks the persona, + persona names the machine. + +**Why.** A real identifier in an example is a leak that outlives its file: +history rewrites cannot recall cached views or merged-PR refs, so prevention +at authoring time is the only cheap point of control. Reserved values are also +*mechanically distinguishable*: when every legitimate example sits inside a +known range, any identifier outside it is flaggable with near-zero false +positives — the convention is what makes a network-identifier lint feasible at +all (iss-154). The 2026-07-29 incident demonstrated the failure mode: a +machine investigation journaled into a committed working file put a real +tailnet IP, two real device names, and the machine's presence patterns on a +public tip, and no gate could object because nothing distinguished a real +identifier from an illustrative one. + +**Bounds.** + +- The rule covers *illustrative* values. Real identifiers that must be handled + (a user's actual config, a probe result) are runtime data — they belong in + the gitignored local tier or the itd-74 private banlist's protection, never + in committed prose; that boundary is itd-74's job, not this rule's. +- Loopback (`127.0.0.1`, `::1`) and unspecified (`0.0.0.0`) addresses are + standard protocol values, not leaks; documenting a bind default is in + bounds. + +**Live instance.** The persona registry (`.abcd/development/personas.json`) +already implements the name half; no fixture-identifier equivalent exists yet +for hosts and addresses. + +**Promotion.** A docs-lint / privacy-hygiene rule that flags any committed +network identifier *outside* the reserved ranges (the allowlist inversion +recorded in iss-154) would make this a discipline, following the itd-79 +promotion path. diff --git a/.abcd/development/research/notes/2026-07-08-confidential-sources-provenance-sota.md b/.abcd/development/research/notes/2026-07-08-confidential-sources-provenance-sota.md index bfabd020..7a8683eb 100644 --- a/.abcd/development/research/notes/2026-07-08-confidential-sources-provenance-sota.md +++ b/.abcd/development/research/notes/2026-07-08-confidential-sources-provenance-sota.md @@ -77,7 +77,7 @@ from the marked entries.** - The two-layer split this repo already runs (public banned tokens in CI lint; private patterns in an untracked pre-commit banlist, because a public rule cannot contain the secret it bans) is the right architecture — - [itd-74](../../intents/drafts/itd-74-name-banlist.md) generalises it. + [itd-74](../../intents/planned/itd-74-name-banlist.md) generalises it. - The new move: *derive* the private patterns from the bibliography's `confidential: true` entries (title, aliases, full author names) so the banlist cannot drift from the corpus. Custom-rule secret scanners at diff --git a/.abcd/development/specs/open/spc-20-name-banlist.md b/.abcd/development/specs/open/spc-20-name-banlist.md new file mode 100644 index 00000000..e3b7105d --- /dev/null +++ b/.abcd/development/specs/open/spc-20-name-banlist.md @@ -0,0 +1,82 @@ +--- +id: spc-20 +slug: name-banlist +intent: itd-74 +--- +# name-banlist + +## Summary + +Two-layer banned-names enforcement, generalised from the hand-wired prototype +already live on abcd-cli, delivered as a capability every abcd-managed repo +inherits through `ahoy`. The **public layer** is a banned-token family in the +deterministic docs-lint (committed config, CI-enforced, per-line escape). The +**private layer** is a gitignored per-machine banlist read by a committed +pre-commit guard, so a private string never appears in tracked content — not +even in the config that bans it. Private entries include machine identifiers +(hostnames, device names, IP/CIDR prefixes) as first-class citizens. + +## Scope + +In: the two banlist stores and their formats; generalising the existing +`harness/*` docs-lint banned-token family into a banned-names family; the +committed guard hook and its refuse-by-key behaviour; `ahoy` scaffolding of +all three surfaces (lint config entry, hook, gitignored stub); banlist +maintenance verbs (add / list / remove) on both layers; honest reporting of +the private layer's local-only reach. + +Out: richer dedicated reporting for the public rule (future work per the +planning interview); CI enforcement of the private layer (impossible by +construction — the point); generic network-identifier pattern detection +(iss-154/157, a separate detector); the from-scratch name scan gating a +public flip. + +## Approach + +- **Two files, per the planning decision.** The public banlist lives in the + committed docs-lint config (extending the existing banned-token family + shape: pattern, severity, allow-context escape). The private banlist is a + gitignored file scaffolded with instructions and reserved-value examples + only (`examples-use-reserved-identifiers`: RFC 5737/3849/2606/7042 values, + persona-derived hostnames). Entry format is key + pattern; the key is the + only thing tooling ever prints. +- **One canonical primitive.** The public family generalises the existing + `harness/*` family in place — same lint engine, same escape hatch — rather + than adding a second banned-token mechanism. The guard hook generalises the + existing `.githooks/pre-commit` prototype. +- **Refuse by key.** The guard greps staged content against private patterns; + on match it exits non-zero naming the entry key only. The matched string + and pattern never reach stdout/stderr/logs. Absent banlist ⇒ loud warning, + exit zero (the layer protects machines that opted in; silence must never + impersonate protection). +- **`ahoy` wiring.** Scaffolding writes: the public family into the repo's + docs-lint config, the committed hook, the gitignore entry, and the stub. + A repo becomes name-safe by being abcd-managed. +- **Verbs.** `abcd`-level add/list/remove operate on both layers; visibility + follows the layer (public entries render fully, private by key). + +## Acceptance-criteria mapping + +1. Public-gate criterion → the generalised banned-names docs-lint family + (blocker severity, file+line findings, allow escape). +2. Refuse-by-key criterion → the guard's output contract; tested with a + fixture banlist asserting the pattern value is absent from all output. +3. Machine-identifier criterion → entry format treats hostname/IP/CIDR + patterns identically to name patterns; fixtures use reserved values. +4. Absent-list criterion → guard's no-file branch: warn loudly, never block, + never silent-pass. +5. Scaffold criterion → `ahoy` writes all four artefacts; scaffold test + asserts stub content contains only reserved-range values. +6. Verb-visibility criterion → list rendering redacts private entries to + keys; asserted in surface tests. +7. Honest-reach criterion → status/report text states CI cannot enforce the + private layer; asserted against the rendered surface. + +## Evidence and lineage + +Prototype: abcd-cli's own docs-lint `harness/*` family, `.githooks/pre-commit` +guard, and gitignored banlist. Motivating incident: the 2026-07-29 +managed-repo NEXT.md privacy-leak investigation (iss-154..iss-158); scope +extension to machine identifiers recorded in iss-158. Related principle: +`examples-use-reserved-identifiers` (seeding). Related discipline path: +itd-79 (registry + lint promotion pattern). diff --git a/.abcd/work/DECISIONS.md b/.abcd/work/DECISIONS.md index cff098c4..d2c6f49c 100644 --- a/.abcd/work/DECISIONS.md +++ b/.abcd/work/DECISIONS.md @@ -878,3 +878,6 @@ parallel-agent merge contention bites. - 2026-07-28 — itd-102 implementation: repo positioning lives in a NEW `internal/core/positioning` package, not `internal/core/identity` — that package is the git commit-author gate (`.abcd/config/identity.json` pin, pre-commit hook) and shares nothing with repo self-description but the English word; folding them would put two unrelated concerns behind one name. The user-facing verb stays `abcd identity` (spc-19). For the same collision the registry is `.abcd/positioning.json` at the `.abcd/` top level (beside docs-lint.json / record-lint.json / rules.json), NOT `.abcd/config/identity.json`. Rejected: extending the commit-identity package; naming the verb `abcd positioning` (spc-19 fixes the verb). - 2026-07-28 — itd-102: surface comparison is normalised CONTAINMENT of each required block field (markup, dashes, wrapping, case folded; trailing sentence punctuation trimmed from the needle), not line equality. Equality would flag abcd's own three conforming surfaces, each of which carries the tagline in a different rendering (inside `

`, concatenated with the pitch in the manifest, bolded mid-sentence and line-wrapped in AGENTS.md); containment catches all three iss-143 variants while accepting all three current ones. Consequence: `identity render` fires only on drift, and its template render is a proposal to adopt by hand, not a byte-exact reproduction of conforming prose. - 2026-07-28 — itd-102: no scaffold entry point existed in ahoy/prepare for a repo record block, so the verb family gained a minimal `identity init` (write path: block + pointer, atomic, adopts an existing block, refuses to repoint an adopted registry). `abcd launch scaffold` is release-machinery-specific and `ahoy install` writes only abcd's own plumbing; extending either would have widened its remit. +- 2026-07-29 — privacy-leak follow-up: illustrative machine identifiers in anything committed or published always come from reserved documentation ranges (RFC 5737 IPv4, RFC 3849 IPv6, RFC 2606 domains, RFC 7042 MACs; hostnames derived from the persona registry, e.g. alice-laptop) — the itd-79 persona rule applied to infrastructure. Recorded as principle `examples-use-reserved-identifiers`; enforcement is the iss-154 allowlist-inversion lint (flag any identifier OUTSIDE the reserved ranges), whose shipping promotes the principle to a discipline. No new intent: real private identifiers are itd-74's banlist territory — the incident evidence extends itd-74's scope to machine identifiers (iss-158) rather than founding a second primitive. Incident issue text deliberately names no repo and no values (shape only), so the ledger itself cannot re-leak. +- 2026-07-29 — v0.5.0 scoped as "security & consistency" (plan: plans/2026-07-29-v0.5.0-security-and-consistency.md): the security half closes the NEXT.md leak class end-to-end (itd-74/spc-20; the atomic iss-154+157+125+153 detector item; iss-155/156; guard batch iss-159+144+148; boundary fixes iss-30/34), the consistency half retires the record-currency majors (iss-37..44, iss-80) — maintainer added the latter rather than deferring them. Version is derived by launch ship, not declared; 0.5.0 is the prediction given itd-74 additive. Deferred majors listed explicitly in the plan (largest: iss-124); the 2026-07-24 queue's pick-up role is superseded. +- 2026-07-29 — v0.5.0 cycle runs as a scheduled cloud loop (one item per 5-hour round, plan order): auto-merge authorised through the strict gate only (CI green + two adversarial MERGE verdicts; security lens mandatory on workstream A/B diffs); itd-74 is in autonomous scope, multi-round via PR resume, bounded by spc-20 and STOP condition 1. Rejected: human-merges-everything (stalls the ordered pipeline at merge cadence) and auto-merge-C/D-only (slows exactly the security half the release is for). diff --git a/.abcd/work/issues/open/iss-153-privacy-hygiene-abspathre-flags-users-shared-and-would-flag.md b/.abcd/work/issues/open/iss-153-privacy-hygiene-abspathre-flags-users-shared-and-would-flag.md new file mode 100644 index 00000000..8b690482 --- /dev/null +++ b/.abcd/work/issues/open/iss-153-privacy-hygiene-abspathre-flags-users-shared-and-would-flag.md @@ -0,0 +1,12 @@ +--- +schema_version: 1 +id: "iss-153" +slug: "privacy-hygiene-abspathre-flags-users-shared-and-would-flag" +severity: "minor" +category: "bug" +source: "agent-finding" +found_during: "prepare-this-repo run against an external repo" +found_at: "internal/core/audit/rule_privacy.go" +--- + +privacy-hygiene absPathRe flags /Users/Shared/... (and would flag /Users/Guest/...) — macOS system directories, not usernames. Exempt well-known non-user segments after /Users/ so product code that legitimately uses /Users/Shared does not need per-line waivers. \ No newline at end of file diff --git a/.abcd/work/issues/open/iss-154-privacy-hygiene-no-network-identifier-patterns.md b/.abcd/work/issues/open/iss-154-privacy-hygiene-no-network-identifier-patterns.md new file mode 100644 index 00000000..e2d983b1 --- /dev/null +++ b/.abcd/work/issues/open/iss-154-privacy-hygiene-no-network-identifier-patterns.md @@ -0,0 +1,14 @@ +--- +schema_version: 1 +id: "iss-154" +slug: "privacy-hygiene-no-network-identifier-patterns" +severity: "major" +category: "bug" +source: "agent-finding" +found_during: "managed-repo NEXT.md privacy-leak investigation 2026-07-29" +found_at: "internal/core/audit/rule_privacy.go" +--- + +privacy-hygiene detects only absolute home paths — no network-identifier patterns (IPv4 incl. CGNAT/tailnet 100.64/10, hostnames, device names, ports-in-context). Field incident: an agent committed a Tailscale investigation (tailnet IP, two device hostnames, firewall posture, service port, presence/away patterns) into a public repo's committed tier and audit passed it silently. The v1 deferral note names only emails and private-repo names; network identifiers are not even recorded as deferred. + +Design note (2026-07-29): the detector should be an allowlist inversion — flag any network identifier NOT inside the reserved documentation ranges (RFC 5737 IPv4 blocks, RFC 3849 2001:db8::/32, RFC 2606 example domains, RFC 7042 doc MACs; loopback/unspecified exempt). The principle `examples-use-reserved-identifiers` (principles/) is what makes this feasible at near-zero false positives: every legitimate committed example uses a reserved value, so anything outside the ranges is a finding. Shipping this lint promotes that principle to a discipline per the itd-79 path. \ No newline at end of file diff --git a/.abcd/work/issues/open/iss-155-three-tier-rule-ignores-local-artefacts-in-committed-tiers.md b/.abcd/work/issues/open/iss-155-three-tier-rule-ignores-local-artefacts-in-committed-tiers.md new file mode 100644 index 00000000..895e90fd --- /dev/null +++ b/.abcd/work/issues/open/iss-155-three-tier-rule-ignores-local-artefacts-in-committed-tiers.md @@ -0,0 +1,12 @@ +--- +schema_version: 1 +id: "iss-155" +slug: "three-tier-rule-ignores-local-artefacts-in-committed-tiers" +severity: "major" +category: "bug" +source: "agent-finding" +found_during: "managed-repo NEXT.md privacy-leak investigation 2026-07-29" +found_at: "internal/core/audit/rule_layout.go" +--- + +three-tier-layout verifies tier presence and the .work.local gitignore but never that local-tier artefacts are absent from committed tiers — a NEXT.md, scratch/, or logs/ under .abcd/work/ or .abcd/development/ passes clean. Field incident: NEXT.md (handover file, local-tier by convention) lived at .abcd/work/NEXT.md in a public repo and carried host infra details; no rule flagged the misplacement. \ No newline at end of file diff --git a/.abcd/work/issues/open/iss-156-pii-domain-misses-network-contexts-and-lacks-ip-hostname-rule.md b/.abcd/work/issues/open/iss-156-pii-domain-misses-network-contexts-and-lacks-ip-hostname-rule.md new file mode 100644 index 00000000..49969c36 --- /dev/null +++ b/.abcd/work/issues/open/iss-156-pii-domain-misses-network-contexts-and-lacks-ip-hostname-rule.md @@ -0,0 +1,12 @@ +--- +schema_version: 1 +id: "iss-156" +slug: "pii-domain-misses-network-contexts-and-lacks-ip-hostname-rule" +severity: "major" +category: "bug" +source: "agent-finding" +found_during: "managed-repo NEXT.md privacy-leak investigation 2026-07-29" +found_at: "internal/core/rules/defaults/rules.json" +--- + +PII rules domain cannot fire on the incident that most needs it: recall keywords (secret, token, credential, pii, redact, hostname, email) miss network/infra contexts (tailscale, vpn, ip, firewall, network, reachability), and the injected rule text carries no never-commit-hostnames-IPs-live-data rule at all — that rule exists only in the parent CLAUDE.md privacy section. An agent writing up a Tailscale investigation gets zero PII injection and zero relevant rule text even if it did. \ No newline at end of file diff --git a/.abcd/work/issues/open/iss-157-scanner-no-network-identifier-patterns.md b/.abcd/work/issues/open/iss-157-scanner-no-network-identifier-patterns.md new file mode 100644 index 00000000..066a510b --- /dev/null +++ b/.abcd/work/issues/open/iss-157-scanner-no-network-identifier-patterns.md @@ -0,0 +1,12 @@ +--- +schema_version: 1 +id: "iss-157" +slug: "scanner-no-network-identifier-patterns" +severity: "minor" +category: "bug" +source: "agent-finding" +found_during: "managed-repo NEXT.md privacy-leak investigation 2026-07-29" +found_at: "internal/adapter/scanner/patterns.go" +--- + +shared scanner (launch dry-run, lifeboat pack, history capture) has token-shaped secret patterns and identity matchers (username, email, home path, github remote) but no network-identifier patterns — a lifeboat pack or launch bundle carrying a tailnet IP, device hostnames, or firewall posture ships clean. Same root gap as the audit rule; fix belongs in the shared primitive, not per-surface. \ No newline at end of file diff --git a/.abcd/work/issues/open/iss-158-private-names-banlist-native-gate.md b/.abcd/work/issues/open/iss-158-private-names-banlist-native-gate.md new file mode 100644 index 00000000..8d28b88e --- /dev/null +++ b/.abcd/work/issues/open/iss-158-private-names-banlist-native-gate.md @@ -0,0 +1,12 @@ +--- +schema_version: 1 +id: "iss-158" +slug: "private-names-banlist-native-gate" +severity: "major" +category: "future-work-seed" +source: "agent-finding" +found_during: "managed-repo NEXT.md privacy-leak investigation 2026-07-29" +found_at: "internal/adapter/scanner" +--- + +no commit-time gate for user-private identifiers: the canonical home for this is itd-74 (name-banlist, in drafts/) — its private-banlist layer (local untracked file + committed guard hook, refuse by entry-key only, never echoing the value) is exactly the gate this incident needed. This issue is NOT a new primitive: it records the field evidence that itd-74's scope must include machine identifiers (hostnames, tailnet IPs/prefixes, device names), not only harness/project names, and that the incident strengthens the case for promoting itd-74 out of drafts (a maintainer decision). Generic patterns (iss-154/157) catch shapes; only the banlist catches this user's actual names. Prior art at skill level: the confidential-sources sync-banlist guard; dogfood prototype already live per itd-74's own record. Complements, does not replace, iss-154. \ No newline at end of file diff --git a/.abcd/work/issues/open/iss-159-hazard-registry-missing-gh-destructive-remote-ops.md b/.abcd/work/issues/open/iss-159-hazard-registry-missing-gh-destructive-remote-ops.md new file mode 100644 index 00000000..54775215 --- /dev/null +++ b/.abcd/work/issues/open/iss-159-hazard-registry-missing-gh-destructive-remote-ops.md @@ -0,0 +1,12 @@ +--- +schema_version: 1 +id: "iss-159" +slug: "hazard-registry-missing-gh-destructive-remote-ops" +severity: "major" +category: "security" +source: "agent-finding" +found_during: "guard coverage check during itd-74 planning 2026-07-29" +found_at: "internal/core/guard" +--- + +hazard 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. \ No newline at end of file diff --git a/.abcd/work/reviews/3de51484c6a7fa30720089421cd31d7031156edd/docs-currency-reviewer.json b/.abcd/work/reviews/3de51484c6a7fa30720089421cd31d7031156edd/docs-currency-reviewer.json new file mode 100644 index 00000000..9759ffd8 --- /dev/null +++ b/.abcd/work/reviews/3de51484c6a7fa30720089421cd31d7031156edd/docs-currency-reviewer.json @@ -0,0 +1,31 @@ +{ + "subject": { "digest": { "gitCommit": "3de51484c6a7fa30720089421cd31d7031156edd" } }, + "verifier": { "id": "abcd:docs-currency-reviewer" }, + "timeVerified": "2026-07-29T05:18:08Z", + "verificationResult": "HOLD", + "judgeModel": "claude-fable-5", + "policy": { "detector": "docs-currency-reviewer", "version": "1" }, + "categories": { + "stale": 2, + "incomplete": 0, + "current": 0 + }, + "failing": [ + { + "file": "docs/README.md", + "lines": "11,14-16", + "verdict": "STALE", + "claim": "The CLI reference under reference/cli/ is described as 'a planned generated reference' and 'a placeholder for a planned generated reference (from the Cobra command tree); that generation is not yet wired, so the live CLI reference today is `abcd --help`'.", + "reality": "The generated CLI reference is wired and shipped: docs/reference/cli/commands.md is produced by GenerateReference in internal/surface/cli/reference.go (//go:generate go run ../../../cmd/abcd-gen-cli-ref), and TestReferenceMatchesCommittedPage in internal/surface/cli/reference_test.go gates the build against drift. The page's own header states it is generated from the command tree.", + "fix": "Rewrite the reference/cli description in present tense to state that reference/cli/commands.md is generated from the command tree and drift-tested; remove 'planned', 'placeholder', and 'not yet wired'." + }, + { + "file": "docs/reference/README.md", + "lines": "10-12", + "verdict": "STALE", + "claim": "'cli/ is a placeholder for a **planned** auto-generated command reference (from the Cobra command tree); that generation is not yet wired, so until it ships the live CLI reference is `abcd --help`.'", + "reality": "Same as above: the generation is wired (GenerateReference / cmd/abcd-gen-cli-ref), the page ships at docs/reference/cli/commands.md, and a build-gating drift test keeps it current.", + "fix": "State in present tense that cli/commands.md is the generated, drift-tested command reference; drop 'placeholder', 'planned', 'not yet wired'." + } + ] +}