Skip to content

fix(engine): verify SLSA build provenance via sigstore-verify (ADR-0020) - #339

Merged
thejefflarson merged 2 commits into
mainfrom
thejefflarson/provenance-verify-sigstore-verify
Aug 26, 2026
Merged

fix(engine): verify SLSA build provenance via sigstore-verify (ADR-0020)#339
thejefflarson merged 2 commits into
mainfrom
thejefflarson/provenance-verify-sigstore-verify

Conversation

@thejefflarson

Copy link
Copy Markdown
Owner

What was wrong

The dashboard's build-provenance column was blank on every row — the observer resolved Absent for every image, including protector's own signed+attested images. The provenance feature (ADR-0020 §5) had been inert since it shipped.

Root cause — two independent dead ends in the sigstore crate (0.14, newest release)

  1. trusted_signature_layers fetches the SLSA bundle as an OCI referrer, then from_sigstore_bundle rejects it — it hardcodes the cosign sign/v1 predicate and errors on https://slsa.dev/provenance/v1. So the shared signing fetch can never surface a SLSA layer.
  2. The crate's other public path, bundle::verify::Verifier, recomputes the Rekor DSSE envelopeHash from a proto round-trip that never equals the hash Rekor stored (a canonicalization bug) — offline DSSE always fails; the online path is an unimplemented stub.

Both confirmed against the live agent image (envelopeHash mismatch). No published sigstore version fixes either, and the Fulcio-chain / SCT primitives needed to hand-roll around them are pub(crate).

Fix

Add sigstore-verify (prefix-dev) for the provenance verify step only — a maintained verifier purpose-built for GitHub artifact attestation that handles the DSSE Rekor-entry consistency correctly and ships a built-in production trust root. Signing stays on the sigstore crate, untouched.

New provenance_observer module: fetch the image's OCI referrers directly, select the slsa.dev/provenance bundle, verify it offline against the same registry (no new egress destination, no transparency-log call), and feed the verified facts into the unchanged classify_provenance / parse_slsa_predicate pipeline — so baseline, drift, and render are all untouched; only the source of the facts moved.

  • Registry without the referrers API → Absent, not Checking (the manifest fetch already proved reachability), so mirrored base images stay calm.
  • No new PROTECTOR_* flag — detection stays on by default.

Validated live

  • protector-agent:0.14.1Verified(source: github.com/thejefflarson/protector, builder: …/agent.yml@refs/tags/v0.14.1)
  • redis (mirror), ollama, argocdAbsent

Tests & gates

Full engine suite (1233) green · clippy clean (warnings-as-errors) · fmt clean · self-containment guard passes · files under the 1000-line cap. Adds 7 unit tests + 2 #[ignore]d live integration tests guarding the exact green-on-fixtures / dead-in-prod failure mode. ADR-0020 amended with the decision (and its two now-stale notes corrected).

ADR-0042 note: provenance_facts / predicate_from_pae were removed from cosign.rs, but classify_provenance / parse_slsa_predicate gained provenance_observer as their live consumer — no shared layer dropped to zero.

🤖 Generated with Claude Code

thejefflarson and others added 2 commits August 25, 2026 00:39
The dashboard provenance column was blank on every row: the observer resolved
Absent for every image, including protector's own signed+attested images. The
feature had been inert since it shipped.

Root cause is two independent dead ends in the sigstore crate (0.14, newest):
its trusted_signature_layers hardcodes the cosign sign/v1 predicate and drops
every SLSA attestation, and its bundle::verify::Verifier recomputes the Rekor
DSSE envelopeHash from a proto round-trip that never matches what Rekor stored
(offline always fails; online is unimplemented). Both confirmed against the live
agent image; the primitives to hand-roll around them are pub(crate).

Add the sigstore-verify crate (prefix-dev) for the provenance verify step only —
it handles GitHub artifact attestation correctly and ships a built-in trust root.
A new provenance_observer module fetches the image's OCI referrers directly,
selects the slsa.dev/provenance bundle, verifies it offline against the same
registry (no new egress), and feeds the unchanged classify_provenance pipeline.
A registry without the referrers API resolves to Absent, not perpetual Checking.

Validated live: protector-agent -> Verified (correct source + builder); redis /
ollama / argocd -> Absent. Adds unit tests plus an ignored live integration test
guarding the exact green-on-fixtures / dead-in-prod failure mode.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…ries

The audit gate flagged two newly-published advisories in pre-existing transitive
deps: h2 (RUSTSEC-2026-0258, unbounded empty DATA frames) and webbrowser
(RUSTSEC-2026-0257, Unix BROWSER argument injection). Both are lock-only,
semver-compatible bumps.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@thejefflarson
thejefflarson enabled auto-merge (squash) August 26, 2026 02:55
@thejefflarson
thejefflarson merged commit 88d7360 into main Aug 26, 2026
5 checks passed
@thejefflarson
thejefflarson deleted the thejefflarson/provenance-verify-sigstore-verify branch August 26, 2026 03:02
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant