Skip to content

Commit 223e8de

Browse files
eastmadcclaude
andcommitted
chore(repo-hygiene): gitignore nested private sibling repo + redact workspace name
(1) Add `cve-assessment-framework/` to .gitignore — the nested private sibling repo (own .git) was untracked but not ignored, so a broad `git add -A` from the repo root could embed it as a gitlink and leak its existence to this public repo. (2) Redact a private Bitbucket workspace name from a Rule #53 provenance note and a session handoff (replaced with "a private Bitbucket workspace"). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
1 parent 5ff07a6 commit 223e8de

3 files changed

Lines changed: 8 additions & 2 deletions

File tree

.gitignore

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,3 +64,9 @@ test-firmware/
6464
backend/vol3-symbols/*.zip
6565
backend/vol3-symbols/SHA256SUMS.tmp
6666
backend/vol3-symbols/*.zip.tmp
67+
68+
# Nested PRIVATE sibling repo (its own .git). It must NEVER be tracked or
69+
# embedded as a gitlink in this PUBLIC repo — a broad `git add -A` from the
70+
# repo root would otherwise leak the private repo's existence + commit pointer
71+
# to public GitHub.
72+
cve-assessment-framework/

.planning/handoffs/session-2026-06-08T17-52-42.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
# HANDOFF — cra-exploitability-deep-review (2026-06-04→06-05 arc)
22

33
**Generated:** 2026-06-08 · **Campaign:** cra-exploitability-deep-review-2026-05-30 (status: active)
4-
**Repos:** wairz (session root; `.planning/` lives here) · cve-assessment-framework (nested, separate .git — all 63 commits this arc; Bitbucket `surgprodsec`, branch `master`, pushed, 0 ahead)
4+
**Repos:** wairz (session root; `.planning/` lives here) · cve-assessment-framework (nested, separate .git — all 63 commits this arc; a private Bitbucket workspace, branch `master`, pushed, 0 ahead)
55
**Knowledge:** `.planning/knowledge/cra-exploit-deep-review-rootcause-teamshare-2026-06-05-{patterns,antipatterns}.md`
66
**Postmortem:** `.planning/postmortems/postmortem-cra-exploit-deep-review-2026-06-05.md`
77

CLAUDE.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -477,7 +477,7 @@ These rules were extracted from recurring bugs and failures across 30+ developme
477477

478478
**Promotable to Rule-of-Four when:** the next adaptable-extension surface (decoder family, vendor adapter, JTAG TAP family, USB descriptor families, etc.) ships under the same shape — closed-grammar YAML + plugin escape hatch + walker dispatch table + Rule #44 cross-firmware tool + Rule #46 paired canary + Wave-1+Wave-2 deep research methodology. Promotion date: 2026-05-22 (Rule-of-Three DURABLE BEYOND DEBATE).
479479

480-
53. **EXTENDS Rule #46 to the RECOGNIZER side of a substantiation gate: a gate that "recognizes" an exception, clear, or disposition MUST match the POSITIVE EVIDENCE behind it, NEVER a bare conclusion-tag that merely asserts the conclusion — a tag-matching recognizer launders unsubstantiated assertions straight past the gate.** Rule #46 establishes that any absence-asserting check ("0 violations / no drift / X is absent") needs a paired canary that synthesizes a violation and confirms REJECTION; that half is unchanged here — cite #46, don't re-derive it. This rule adds the second failure mode #46 doesn't cover: an absence-gate can have a perfect canary and STILL launder, if its allowlist/skip recognizer keys off a *marker* an upstream step writes rather than the *evidence* that marker is supposed to certify. The load-bearing distinction: a compliant recognizer regex matches a CONTENT token that constitutes proof (`CONFIG_X=n`, a version range, a named-foreign-vendor set, a concrete reachability artifact); a laundering recognizer matches a disposition-enum name or a bracketed rule-id (`[R47-L1]`, `platform_mismatch`, `MISATTR-FP`) as a standalone token. **Resolved-aware SKIP of an adjudication marker is permitted ONLY under a two-part test:** (1) the marker is emitted by a deliberate evidence-recording step in the SAME pipeline (not hand-typed ad hoc), AND (2) the substantiating evidence travels WITH the marker in the very artifact the gate reads — so the gate re-validates the evidence, not the label. A bare-tag membership test that trusts the tag without re-reading the evidence beside it is itself the laundering hole. **Axiom-4 binding** (the guilty-until-proven discipline this campaign runs on): the highest-risk dispositions to launder are the ones that *clear* risk (`not_reachable` / `not_affected` / `safe`), so a recognizer for those MUST key on immutable proof, NEVER on a mutable runtime annotation — "module not loaded" / "service inactive" / "currently unreachable" describe a state that can flip (an autoloadable kernel module, a disabled-but-present daemon), and matching them clears on absence-of-evidence, which Axiom 1 forbids. **Mechanical review-time gate:** grep every recognizer/allowlist regex in your gates for disposition-enum names or rule-id tokens used as standalone matches (vs. as a required prefix to an evidence token); each hit is a laundering candidate. Ship the recognizer's own canary: stamp a bare tag with NO evidence into a synthetic record, run the gate, confirm it FLAGS it (not skips it). **Wairz-native anchor:** the security-gate family (Rule #36 no-execute, Rule #45 no-decrypt, Rule #37 trust-anchor) has the same shape — a gate that trusts a self-asserted `signed=true` / `safe=true` / `no_decrypt_ok` label instead of re-deriving the proof (re-running signify, re-tokenizing the walker source, re-checking the SHA pin) is a tag-matcher; the wairz versions correctly re-derive, and Rule #53 names why that's load-bearing. **Evidence (Rule-of-One-plus; self-introduced + caught by self-audit, which is the honest framing — the discipline's value showed in catching its own violation):** (1) PRIMARY — the CR-1 over-clear gate's `R47-L1` recognizer matched 617 BARE `platform_mismatch [R47-L1]` tags carrying zero CPE evidence and laundered them past the gate (an Axiom-1 false-clear class I introduced); a self-audit caught it; the tags were un-laundered (`7c6f994b`), 20 re-substantiated with real CPE evidence and 597 re-opened guilty. (2) SECONDARY — the discipline then HELD: applying the heads-deepdive `not_reachable` demotions, I deliberately did NOT add a blanket `HEADS-DEEPDIVE` recognizer to CR-1; letting it scrutinize each rationale, it REJECTED 2 weak demotions ("module not loaded" for USB-autoloadable `ath9k_htc`/`gs_usb` — a mutable annotation, not non-reachability proof) and they were re-opened chainable, not laundered (`82f9aae8` arc). This session's `audit_misattribution.py` hardening (`b0aab59a`) operationalizes the two-part SKIP test: `_fp_resolved()` honours a `MISATTR-FP` tag only when ≥30 chars of evidence-matching rationale travels with it, and `_fp_meta_canary()` confirms a bare tag stays flagged. **Provenance note:** the worked examples + SHAs above originate in the sibling `cve-assessment-framework` repo (Bitbucket `surgprodsec`), not wairz — the rule is wairz-canonical because the *shape* (recognizer-vs-evidence, security-gate analog) recurs across both. **Companions:** Rule #46 (the absence-canary half this extends), #17 + #24 (silent-CLI-exit canary — same "tool confirmed nothing vs wasn't looking" family), #35a (pipe-induced exit obfuscation — verification-artifact-that-lies), #35b (mock vs live-canary — "X was called" vs "X was called with the right args"), #19 (evidence-first — clear only on positive evidence), #36 + #45 + #37 (the security-gate analog that must re-derive, not trust a label). Promotable to Rule-of-Two when a second independent tag-matching laundering hole surfaces in any gate. Promotion date: 2026-06-05.
480+
53. **EXTENDS Rule #46 to the RECOGNIZER side of a substantiation gate: a gate that "recognizes" an exception, clear, or disposition MUST match the POSITIVE EVIDENCE behind it, NEVER a bare conclusion-tag that merely asserts the conclusion — a tag-matching recognizer launders unsubstantiated assertions straight past the gate.** Rule #46 establishes that any absence-asserting check ("0 violations / no drift / X is absent") needs a paired canary that synthesizes a violation and confirms REJECTION; that half is unchanged here — cite #46, don't re-derive it. This rule adds the second failure mode #46 doesn't cover: an absence-gate can have a perfect canary and STILL launder, if its allowlist/skip recognizer keys off a *marker* an upstream step writes rather than the *evidence* that marker is supposed to certify. The load-bearing distinction: a compliant recognizer regex matches a CONTENT token that constitutes proof (`CONFIG_X=n`, a version range, a named-foreign-vendor set, a concrete reachability artifact); a laundering recognizer matches a disposition-enum name or a bracketed rule-id (`[R47-L1]`, `platform_mismatch`, `MISATTR-FP`) as a standalone token. **Resolved-aware SKIP of an adjudication marker is permitted ONLY under a two-part test:** (1) the marker is emitted by a deliberate evidence-recording step in the SAME pipeline (not hand-typed ad hoc), AND (2) the substantiating evidence travels WITH the marker in the very artifact the gate reads — so the gate re-validates the evidence, not the label. A bare-tag membership test that trusts the tag without re-reading the evidence beside it is itself the laundering hole. **Axiom-4 binding** (the guilty-until-proven discipline this campaign runs on): the highest-risk dispositions to launder are the ones that *clear* risk (`not_reachable` / `not_affected` / `safe`), so a recognizer for those MUST key on immutable proof, NEVER on a mutable runtime annotation — "module not loaded" / "service inactive" / "currently unreachable" describe a state that can flip (an autoloadable kernel module, a disabled-but-present daemon), and matching them clears on absence-of-evidence, which Axiom 1 forbids. **Mechanical review-time gate:** grep every recognizer/allowlist regex in your gates for disposition-enum names or rule-id tokens used as standalone matches (vs. as a required prefix to an evidence token); each hit is a laundering candidate. Ship the recognizer's own canary: stamp a bare tag with NO evidence into a synthetic record, run the gate, confirm it FLAGS it (not skips it). **Wairz-native anchor:** the security-gate family (Rule #36 no-execute, Rule #45 no-decrypt, Rule #37 trust-anchor) has the same shape — a gate that trusts a self-asserted `signed=true` / `safe=true` / `no_decrypt_ok` label instead of re-deriving the proof (re-running signify, re-tokenizing the walker source, re-checking the SHA pin) is a tag-matcher; the wairz versions correctly re-derive, and Rule #53 names why that's load-bearing. **Evidence (Rule-of-One-plus; self-introduced + caught by self-audit, which is the honest framing — the discipline's value showed in catching its own violation):** (1) PRIMARY — the CR-1 over-clear gate's `R47-L1` recognizer matched 617 BARE `platform_mismatch [R47-L1]` tags carrying zero CPE evidence and laundered them past the gate (an Axiom-1 false-clear class I introduced); a self-audit caught it; the tags were un-laundered (`7c6f994b`), 20 re-substantiated with real CPE evidence and 597 re-opened guilty. (2) SECONDARY — the discipline then HELD: applying the heads-deepdive `not_reachable` demotions, I deliberately did NOT add a blanket `HEADS-DEEPDIVE` recognizer to CR-1; letting it scrutinize each rationale, it REJECTED 2 weak demotions ("module not loaded" for USB-autoloadable `ath9k_htc`/`gs_usb` — a mutable annotation, not non-reachability proof) and they were re-opened chainable, not laundered (`82f9aae8` arc). This session's `audit_misattribution.py` hardening (`b0aab59a`) operationalizes the two-part SKIP test: `_fp_resolved()` honours a `MISATTR-FP` tag only when ≥30 chars of evidence-matching rationale travels with it, and `_fp_meta_canary()` confirms a bare tag stays flagged. **Provenance note:** the worked examples + SHAs above originate in the sibling `cve-assessment-framework` repo (a private Bitbucket workspace), not wairz — the rule is wairz-canonical because the *shape* (recognizer-vs-evidence, security-gate analog) recurs across both. **Companions:** Rule #46 (the absence-canary half this extends), #17 + #24 (silent-CLI-exit canary — same "tool confirmed nothing vs wasn't looking" family), #35a (pipe-induced exit obfuscation — verification-artifact-that-lies), #35b (mock vs live-canary — "X was called" vs "X was called with the right args"), #19 (evidence-first — clear only on positive evidence), #36 + #45 + #37 (the security-gate analog that must re-derive, not trust a label). Promotable to Rule-of-Two when a second independent tag-matching laundering hole surfaces in any gate. Promotion date: 2026-06-05.
481481

482482
54. **A fact with ONE source-of-truth and N DERIVED representations that a downstream consumer ingests drifts SILENTLY when the source is patched in place — derive-don't-hand-maintain, regenerate ALL representations in the same write step, and gate cross-representation consistency (with a source-sanity floor, because a green parity gate proves AGREEMENT, not CORRECTNESS).** Niche carve-out against three neighbours: Rule #47 governs CODE that *reacts* to a state change (consumer hooks to rewire); Rule #35c governs read-boundary *normalization* of a JSONB shape at ingest; Rule #54 governs stale DATA COPIES — exported/rendered derivations (an alternate-format export, a package mirror, a generated report, a hand-typed current-state figure in prose) that a consumer reads directly and that must be regenerated from the source at WRITE time, never patched in the copy. Litmus: a hand-typed number or an alternate-format export = #54; a background poller/hook = #47; an `_normalize_*` at a read site = #35c. **Three sub-rules:** (a) DERIVE don't hand-maintain — wire every current-state figure to the single source via a regenerating span/template (the `<!--C:key-->value<!--/C-->` span + `refresh_package_docs.py` pattern); never hand-type a number that already exists in the canonical record. (b) On ANY source change, regenerate ALL derived representations in the SAME step and run a consistency gate (derived counts == source counts, version stamps agree). (c) The drift is invisible precisely because the source looks perfect — only a cross-representation diff, or a consumer reading the stale copy, reveals it. **SOURCE-SANITY guard clause (the non-obvious half):** "regenerate ALL from source" propagates a WRONG source to every consumer at once and the parity gate reports GREEN (uniformly wrong is still consistent) — strictly worse than visible hand-divergence, because nothing looks off. So the canonical/source record must ITSELF be derived-or-gated against ground truth: the consistency gate MUST include a source-sanity assertion (canonical-state per-device count == the source-export's own count) so a green result means agreement AND correctness, not just agreement. **NOT-APPLY boundary:** archived or date-stamped point-in-time captures stay frozen by design — `output/vex_history/`, dated snapshots, changelogs, postmortems, `*.bak`, and any "as of <date>" prose are deliberate records of a past state, NOT derived current-state, and regenerating them would corrupt the audit trail. Litmus: a version- or date-stamped path is a capture — leave it. **Enforceability / mechanization:** (i) regenerate via a single command with a `--check` mode (`refresh_package_docs.py --check` is the doc-number gate, LANDED — it diffs every span against canonical and exits non-zero on drift, and includes a malformed-span guard so a broken closer can't freeze a value undetected); (ii) the VEX cross-representation gate (`audit_vex_representations.py`, GATE LANDED this session — PARITY across OpenVEX + CycloneDX + the 2 package mirrors AND the SOURCE-SANITY floor, with a Rule #46 meta-canary); (iii) when enumerating "what reads a COPY of this?", use the Rule #31 width-canary grep discipline (narrow patterns under-count derived representations). **Evidence (Rule-of-Two):** (1) the canonical OpenVEX was current (v107/v88/v86; affected 4,739/11,511/6,777) while the CycloneDX exports + package-mirror VEX had drifted to stale versions — the SBOM-consumer / Notified-Body artifacts silently lagged thousands of affected statements; the USER caught it (no gate existed), fixed by regenerating all 6 representations (`2d724d49`) and then shipping the standing `audit_vex_representations.py` gate (`b0aab59a`) so it can't recur. (2) hand-authored current-state numbers across ~10 conformance docs went stale every revision round; the durable fix was span-wiring each to canonical (the malformed-span freeze that defeated a derivation which LOOKED wired but wasn't is the Rule #53 incident, cross-referenced — its derived-value impact belongs here, its laundering-blindness belongs there). **Wairz-native anchor:** Rule #9 (frontend `Record<UnionType, Config>` maps are a DERIVED copy of a backend enum — adding a backend value without regenerating the frontend map is exactly this drift, crashing React with `undefined`); generated SBOM / HBOM exports (a derived view of the components table); Rule #21 (the CLAUDE.md ↔ `.mex/context/conventions.md` Verify Checklist mirror is a derived copy that must be regenerated in the same commit). **Provenance note:** the VEX/canonical SHAs above originate in the sibling `cve-assessment-framework` repo, not wairz; the rule is wairz-canonical because the derived-copy-drift shape recurs across both (frontend enum maps, SBOM exports, the conventions.md mirror). **Companions:** Rule #47 (consumer-hook enumeration — same "enumerate every dependent before changing an invariant" shape, but for reacting code vs stale data), #35c (JSONB single-source normalizer/stamp — read-side dual of this write-side rule), #48 (cross-stack alignment test — the in-repo source-of-truth-agreement analog), #21 (companion-file sync), #9 (frontend Record maps derived from backend enums), #8 + #26 (rebuild stale derived container images — the same drift at the image layer), #46 (the source-sanity floor's meta-canary). Promotable to Rule-of-Three when a third derived-representation drift class surfaces. Promotion date: 2026-06-05.
483483

0 commit comments

Comments
 (0)