Skip to content

UCA-1: keyless verify never runs Rekor Merkle inclusion proof #137

Description

@avrabe

Surfaced by STPA-Sec analysis recorded in docs/security/stpa-keyless-2026-05-25.md, section 4 — UCA-1. PR #136 (v0.9.1) closed two other findings from that report; this is a remaining gap.

What's missing

`KeylessVerifier::verify` calls only `RekorKeyring::verify_set` (`signer.rs:612`). The Merkle inclusion proof verifier exists at `rekor_verifier.rs:724` and a wrapper at `format.rs:437`, but neither is ever called from the production verify path.

Verification trail:

  • `grep -n 'verify_inclusion_proof\|verify_rekor_inclusion\|verify_entry' src/lib/src/signature/keyless/signer.rs` → 0 hits.
  • `grep -rn 'verify_rekor_inclusion'` → only the definition (`format.rs:452`) and a unit test (`format.rs:787`).

Why it matters

Rekor's SET attests "Rekor signed this `(body, integratedTime, logID, logIndex)` tuple at some point." It does not prove inclusion in the Merkle tree. A misbehaving Rekor instance, a Rekor key compromise, or a Rekor operator producing parallel "side log" entries that are signed but never integrated would still pass `verify_set`. SLSA L4 non-falsifiability is dropped.

Cross-ref

Strengthens the prior audit's C-5 finding (which framed this as "inclusion-proof failures silently downgraded to SET-only fallback"). The actual code is stronger than C-5 stated: there is no inclusion-proof attempt at all, so nothing to fall back from.

Suggested fix

After `verify_set` succeeds at `signer.rs:612`, also call:

```rust
verifier.verify_inclusion_proof(&keyless_sig.rekor_entry)?;
```

Cache the verified inclusion proof in the same cache slot. See also UCA-4 (#TBD) — cache-consumption semantics need fixing in the same change for the inclusion-proof bypass and the cache-skip bypass not to compose.

Acceptance

  • New step in `KeylessVerifier::verify` between `verify_set` and `verify_artifact_binding` (or fold into the existing Rekor block).
  • Test: end-to-end fixture with a tampered `inclusion_proof` field must reject with `VerificationFailed`.
  • Test: missing `inclusion_proof` must reject (fail-closed).
  • CHANGELOG entry under next patch release.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions