CAIN-42 IP hardening, batch 3 (2026-10-07)
WE ATTACKED THE CHECKS THAT DECIDE AND THE VERIFIERS THAT PROVE. 11 DEFECTS CLOSED, LIVE ON ALL THREE DOMAINS.
What was fixed
- PBFT commit certificates (00553056). Before: a PREPARE_QC (a quorum prepared, nothing committed) was accepted as a commit by the gateway, snapshot re-verification and the public verifiers; one lying replica could make the hosted consensus stage authorize. Now: every consumer of 'committed' requires a COMMIT_QC or FAST_COMMIT_QC; all 12 published PBFT bundles still verify.
- signed decision records (642830a7). Before: caller text containing the 0x1F field separator let a database writer re-split a DENIED record into an ALLOWED one with the same digest, so the original signature still verified. Now: 0x1F in caller text is replaced; a digest refuses it; the public verifier gains an ENCODING check (0 of 9,316 live records affected).
- offline evidence verifier (eec2b1fc). Before: printed ALL PASSED for a DENY record re-signed as ALLOW with a forger's own key, for an empty bundle and for a single file with no records. Now: signer key must be pinned; binding fields hashed; one tenant and unique ids per bundle; empty bundle fails.
- CATCP control evaluator and gate (25e8042d). Before: four ALLOW holes: missing revocation state treated as not revoked; tool not bound to its context; denied actions never enforced; risk ceilings not attenuated. Now: each denies; the Python and Go clean-room verifiers now check every attenuation rule.
- trust kernel primitives (6a29f185). Before: the public verify primitive returned VALID for an unsigned, made-up capability token; self-signed passports were VALID; algorithm manifests could be unsigned. Now: signature required and the signer must be the kernel's key; malformed expiry fails closed.
- live-cluster browser verifier (bbb5c3a7). Before: a PREPARE_QC in the commit slot was shown as 'COMMIT votes' / 'CHAIN INTACT'. Now: the page requires a deciding certificate in the commit slot.
- hosted risk screen (69ee0b9b). Before: nesting the payload one level ({'args': {'q': ...}}) left no scannable text, and 'skipped' is not a denial. Now: every key and scalar leaf is screened; a payload deeper than 64 levels blocks.
- human-approval rule (96104a15). Before: an unreadable approval threshold or spend ('$100', NaN) switched the approval rule off. Now: unreadable values hold the action for a human.
- public universal-proof and WORM verifiers (4771fe3a). Before: a self-signed universal proof with no sub-proofs was VALID; an unsigned WORM package was VERIFIED_AUTHENTIC (both reproduced live before the fix). Now: issuer keys pinned; empty composition is INSUFFICIENT_EVIDENCE; WORM requires this node's signature.
- cross-benchmark attestation (b3f55b6e). Before: verify-attestation trusted the key inside the report, so invented benchmark results verified. Now: only the engine's own attestation key is accepted.
- live decision stream (a450297c). Before: recorded decisions never reached SSE/SIEM subscribers (only dry runs did, and those were dropped). Now: published after every successful record.
How it was proven
Each fix has a test that failed against the old code. Proof run: 314 of 315 pass.
1 test failed: tests.test_pbft_qc_public_bundle::test_published_copies_are_identical_and_verifier_matches_source. It is not a batch-3 defect: an uncommitted edit by another session changed a published PBFT evidence page so it no longer matches its source. It is reported, not hidden.
Live check 2026-10-07T19:19:49Z: a self-signed universal proof with no sub-proofs was refused 12 of 12 times across cainstudio.online, mcpgate.online and clawx.click (4 requests each, so both gateway instances answer).
Files: RESULTS.json · junit.xml · manifest.json (SHA-256 of every file). Source code is not published.
Limits. Same-project tests, not third-party assurance; most fixes are proven by tests, one by live attack. PRE-PRODUCTION.