Skip to content

fix(keyless): bind proof-cache key to full entry + propagate audit-hash error (v0.9.2)#141

Merged
avrabe merged 1 commit into
mainfrom
fix/keyless-verify-inclusion-proof-and-cache
May 29, 2026
Merged

fix(keyless): bind proof-cache key to full entry + propagate audit-hash error (v0.9.2)#141
avrabe merged 1 commit into
mainfrom
fix/keyless-verify-inclusion-proof-and-cache

Conversation

@avrabe
Copy link
Copy Markdown
Contributor

@avrabe avrabe commented May 29, 2026

Continues the STPA-Sec keyless-verify hardening campaign from v0.9.1 (#136). Closes UCA-4 (#139) and UCA-5 (#140) from docs/security/stpa-keyless-2026-05-25.md.

⚠️ UCA-1 (#137) was scoped in but descoped — important finding

Wiring the existing Merkle inclusion-proof verifier into verify() (the original plan for this release) revealed via the "Sign & Verify Example" CI job that the verifier recomputes the wrong Merkle root for fresh production Rekor entries on the log2025-* shards (Rekor v2 / tiled-log migration):

Rekor SET verified successfully
Rekor error: Computed root hash does not match expected root.
  Computed: a3dd3a1b…  Expected: b32b6966…   (log index 1672253805)

The verifier had never been on the production path, so this latent breakage was never observed. Enabling it fail-closed would reject all legitimate keyless signatures — strictly worse than the silent gap. UCA-1 is reclassified on #137 from "wire in the existing verifier" to "fix the verifier against Rekor v2, then wire it in". UCA-3 (#138) remains deferred (clock-policy decision).

What this PR fixes

Issue UCA Fix
#139 UCA-4 — proof-cache hit skipped SET re-verify, trusted attacker bundle fields CacheKey::from_entry binds every entry field (length-prefixed, unambiguous) instead of (module_hash, uuid). A hit now requires a byte-identical entry that already passed SET verification.
#140 UCA-5 — audit log recorded SHA-256 of zero bytes on serialize failure Propagate the serialize error (? instead of .ok()), so a serialize failure aborts verify instead of writing a false artifact identity to the forensic trail.

Verification / oracles

  • UCA-4 has a pure-function mechanical oracle: CacheKey::from_entry tests prove every field mutation changes the key, plus a field-boundary-ambiguity test (("ab","c")("a","bc")).
  • UCA-5: serialize call now propagates via map_err(..)?; no .ok() remains on it.
  • A cold clean-room subagent verified all six original claims with file:line citations before the UCA-1 descope; the cache-binding and audit-hash claims it confirmed are exactly what ships here.
  • 609 library tests pass; full workspace builds.

Falsification statement

A keyless proof-cache hit can no longer be induced by mutating the embedded Rekor entry while keeping the module bytes — any field change forces full SET re-verification. A module that fails to serialize no longer records sha256:e3b0c442…b855 in the audit log; it aborts with a non-zero exit.

Closes #139
Closes #140

🤖 Generated with Claude Code

…sh error (v0.9.2)

Continues the STPA-Sec keyless-verify hardening from v0.9.1 (#136),
closing two of the three clear-cut follow-ups from
docs/security/stpa-keyless-2026-05-25.md.

UCA-4 (#139): bind the proof-cache key to the full Rekor entry content
via CacheKey::from_entry (every field, length-prefixed) instead of just
(module_hash, uuid) — both attacker-supplied in the bundle. A cache hit
can now only occur for a byte-identical entry that already passed SET
verification, removing the defence-in-depth loss where a mutated entry
rode a stale cache slot past the SET re-check on a cache hit.

UCA-5 (#140): propagate the module serialize error in the audit-hash
computation (? instead of .ok()), so a serialize failure aborts verify
instead of recording the SHA-256 of zero bytes as a false artifact
identity in the audit log and proof-cache key.

UCA-1 (#137) was scoped in but DESCOPED: wiring the existing Merkle
inclusion-proof verifier into verify() revealed it recomputes the wrong
root for fresh production Rekor entries on the log2025-* shards (Rekor v2
/ tiled-log migration) — the SET verifies but the computed root != the
proof's root_hash. Enabling it fail-closed would reject all legitimate
keyless signatures, so it stays unwired until the verifier is fixed.
Reclassified on #137. UCA-3 (#138) remains deferred (clock-policy).

Four new unit tests (3 cache-key binding + 1 fail-closed inclusion-proof
building block); 609 library tests pass. Clean-room verified.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@avrabe avrabe force-pushed the fix/keyless-verify-inclusion-proof-and-cache branch from 9637ad8 to 4b8ca93 Compare May 29, 2026 19:00
@avrabe avrabe changed the title fix(keyless): close three STPA UCAs — inclusion proof, cache binding, audit hash (v0.9.2) fix(keyless): bind proof-cache key to full entry + propagate audit-hash error (v0.9.2) May 29, 2026
@avrabe avrabe merged commit 158b333 into main May 29, 2026
18 checks passed
@avrabe avrabe deleted the fix/keyless-verify-inclusion-proof-and-cache branch May 29, 2026 19:40
@codecov
Copy link
Copy Markdown

codecov Bot commented May 29, 2026

Codecov Report

❌ Patch coverage is 88.75000% with 9 lines in your changes missing coverage. Please review.

Files with missing lines Patch % Lines
src/lib/src/signature/keyless/signer.rs 0.00% 5 Missing ⚠️
src/lib/src/signature/keyless/format.rs 72.72% 3 Missing ⚠️
src/lib/src/signature/keyless/proof_cache.rs 98.43% 1 Missing ⚠️

📢 Thoughts on this report? Let us know!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

1 participant