Skip to content

refactor(security): switch to sigstore-rust verification#9260

Merged
jdx merged 19 commits into
mainfrom
codex/use-sigstore-rust-verify
May 13, 2026
Merged

refactor(security): switch to sigstore-rust verification#9260
jdx merged 19 commits into
mainfrom
codex/use-sigstore-rust-verify

Conversation

@jdx

@jdx jdx commented Apr 19, 2026

Copy link
Copy Markdown
Owner

Summary

Replace the external sigstore-verification 0.2.x dependency with a local mise-sigstore adapter built on sigstore-verify 0.7.0 from sigstore-rust. mise's call sites are unchanged — the wrapper at src/github/sigstore.rs and the vfox crate are rerouted to the new adapter, which preserves the existing helper-style API (verify_github_attestation, verify_cosign_signature, verify_slsa_provenance, detect_attestations).

Why a local adapter rather than sigstore-verify directly

sigstore-verify 0.7.x exposes a different (and richer) API surface than the legacy crate. The adapter keeps mise's existing helper-style API stable, layers in GitHub-API attestation fetching (including bundle_url resolution + Snappy decompression), and adds the two paths sigstore-verify itself does not cover:

  • Legacy cosign v1 bundles ({base64Signature, cert, rekorBundle}) — emitted by cosign sign-blob --bundle 2.x and still produced by goreleaser-signed releases (envsense, etc.). sigstore-verify's Bundle::from_json rejects this shape, so we chain-validate the embedded Fulcio cert ourselves and ECDSA-verify base64Signature over the artifact bytes.
  • Raw DSSE envelopes (*.intoto.jsonl from slsa-github-generator / goreleaser) — no verificationMaterial, so sigstore-verify rejects them. We verify the DSSE signature against the Fulcio cert in the envelope (chain-validated), then enforce the artifact's SHA-256 is present in the in-toto subject list.

What sigstore-verify 0.7.0 buys us over 0.6.x

0.7.0 (and the matching sigstore-trust-root 0.7.0) lets us drop every local workaround the original adapter shipped:

  • The embedded Sigstore TUF root is gone — upstream ships TUF v14 directly, so TrustedRoot::production() walks the live TUF CDN with no custom bootstrap root from us.
  • The embedded github_trusted_root.json is gone — TrustedRoot::from_embedded(SigstoreInstance::GitHub) provides it (byte-identical content), same fallback rationale: tough doesn't yet validate GitHub's TUF spec version.
  • The manual webpki chain validation for GitHub-internal certs is gone — VerificationPolicy::skip_sct() (new in 0.7.0) lets us turn off just the SCT check (which GitHub doesn't issue) while keeping full sigstore-verify-driven chain validation. We still use verify_cert_chain for raw DSSE envelopes, since those don't go through sigstore_verify::verify at all.

Net delta vs. the previous iteration of this PR: ~1,150 lines deleted from mise-sigstore, plus the legacy cosign v1 path added.

Verified

  • cargo check, cargo clippy --all-targets -- -D warnings, cargo fmt --all -- --check, taplo fmt
  • cargo test --bin mise — 924 tests pass
  • cargo test -p mise-sigstore — 8 tests pass
  • e2e (locally): test_aqua_github_attestations, test_aqua_cosign, test_lockfile_cosign_top_level_binary, test_lockfile_cosign_opts_only, test_python_github_attestations, test_ruby_github_attestations all pass — including the new legacy-bundle path against envsense 0.3.4 and the modern bundle path against goreleaser 2.14.1.

This PR was generated by an AI coding assistant.


Note

High Risk
Replaces the core artifact attestation/cosign/SLSA verification dependency and logic, including new certificate-chain and DSSE/legacy bundle handling, which could incorrectly accept or reject releases if any edge case is missed.

Overview
Swaps out the sigstore-verification dependency for a new workspace crate, mise-sigstore, built on sigstore-verify v0.7, and reroutes mise and vfox verification call sites to it.

The new adapter adds GitHub attestation fetching (including bundle_url resolution and Snappy decompression), updates verification policy handling (e.g., skipping tlog or SCT checks when bundles lack those proofs), and expands support to legacy cosign v1 bundles and raw DSSE *.intoto.jsonl provenance by manually chain-validating embedded Fulcio certs and verifying signatures + subject digests.

Also updates error classification in the GitHub backend to treat more sigstore-verify parse/format failures as non-fatal format issues, adjusts TLS feature wiring, adds release automation for publishing mise-sigstore, and updates deny.toml ignores for transitive rustls-webpki advisories.

Reviewed by Cursor Bugbot for commit 29fa282. Bugbot is set up for automated code reviews on this repo. Configure here.

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request replaces the sigstore-verification dependency with a new internal crate, mise-sigstore, which leverages sigstore-verify v0.6.4. The transition involves updating various backends and plugins to use the new verification helpers. Several issues were identified in the new implementation, including the use of unstable Rust features like the 2024 edition and let_chains syntax. Additionally, feedback was provided regarding potential performance bottlenecks from synchronous I/O in asynchronous contexts and redundant loading of the production trust root within loops. Logic errors in error matching for SLSA fallback and unused variables in URL construction were also noted.

Comment thread crates/mise-sigstore/Cargo.toml
Comment thread crates/mise-sigstore/src/lib.rs Outdated
Comment thread crates/mise-sigstore/src/lib.rs Outdated
Comment thread src/backend/github.rs Outdated
Comment thread crates/mise-sigstore/src/lib.rs Outdated
Comment thread crates/mise-sigstore/src/lib.rs Outdated
Comment thread crates/mise-sigstore/src/lib.rs Outdated
@greptile-apps

greptile-apps Bot commented Apr 19, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

This PR replaces the external sigstore-verification 0.2.x dependency with a local mise-sigstore adapter built on sigstore-verify 0.7.0, keeping mise's existing helper-style API (verify_github_attestation, verify_cosign_signature, verify_slsa_provenance) unchanged at call sites while adding support for legacy cosign v1 bundles and raw DSSE SLSA envelopes.

  • crates/mise-sigstore — New workspace crate that handles GitHub attestation fetching (with bundle_url/Snappy support), modern sigstore bundle verification via sigstore_verify::verify, legacy cosign v1 bundle verification (cert chain validation + ECDSA over artifact bytes), and raw DSSE envelope verification (verify_dsse_signature + verify_cert_chain + PAE signature check).
  • src/github/sigstore.rs — New bridge module that centralises GitHub token resolution via the full resolve_token_for_api_url chain, preventing the unauthenticated-attestation regression that hit rate limits.
  • src/backend/github.rs — Adds is_slsa_format_issue to distinguish format/parse failures from cryptographic failures; deny.toml acknowledges four rustls-webpki 0.102.x advisories pulled transitively via sigstore-tsa 0.7.0.

Confidence Score: 4/5

Safe to merge after reviewing the one inline comment; the security-critical verification paths are substantially improved over the previous iteration.

The new verify_dsse_signature + verify_cert_chain path for raw DSSE envelopes now performs full cryptographic chain validation and PAE signature verification, closing the gaps flagged in earlier review rounds. The one fresh concern is in is_slsa_format_issue: it matches a broad substring (missing field) inside Verification/Sigstore error messages, and a future upstream message-text change could silently misclassify a cryptographic failure as a format issue.

src/backend/github.rs — the is_slsa_format_issue error-message pattern matching warrants a closer look.

Important Files Changed

Filename Overview
crates/mise-sigstore/src/lib.rs New core crate implementing all verification paths. The major security issues from the previous review are addressed: DSSE signatures are now cryptographically verified via verify_dsse_signature + verify_cert_chain, skip_tlog is conditional on bundle.has_inclusion_proof(), the signer identity check properly rejects empty/absent identities, and verify_cosign_signature_with_key propagates verification failures as Err.
src/github/sigstore.rs Clean bridge module that centralises token resolution via resolve_token_for_api_url and prevents unauthenticated attestation requests. Dispatches correctly between default and enterprise API URLs. New tests cover all token source precedences.
src/backend/github.rs Adds is_slsa_format_issue to distinguish format mismatches from verification failures. Relies on fragile upstream error-message substrings — see inline comment.
crates/vfox/src/vfox.rs Call sites updated from sigstore-verification to mise-sigstore. All await?/propagation patterns correct.
deny.toml Four new RUSTSEC ignores for rustls-webpki 0.102.x advisories transitively pulled by sigstore-tsa 0.7.0. Rationale is plausible and matches the code.
xtasks/release-plz mise-sigstore is published before vfox so the version dep is available when vfox is bumped. cargo add --no-default-features correctly leaves workspace-level feature re-exports intact.

Fix All in Claude Code

Reviews (24): Last reviewed commit: "chore(deps): ignore rustls-webpki 0.102 ..." | Re-trigger Greptile

Comment thread crates/mise-sigstore/src/lib.rs Outdated
Comment thread crates/mise-sigstore/src/lib.rs Outdated
Comment thread crates/mise-sigstore/src/lib.rs Outdated
Comment thread crates/mise-sigstore/src/lib.rs
Comment thread crates/mise-sigstore/src/lib.rs
Comment thread crates/mise-sigstore/src/lib.rs
Comment thread crates/mise-sigstore/Cargo.toml Outdated
Comment thread crates/mise-sigstore/src/lib.rs Outdated
Comment thread crates/mise-sigstore/src/lib.rs Outdated
Comment thread crates/mise-sigstore/src/lib.rs
@jdx jdx force-pushed the codex/use-sigstore-rust-verify branch from f4119a1 to 873b1fa Compare April 19, 2026 17:49
Comment thread crates/mise-sigstore/src/lib.rs Outdated
Comment thread crates/mise-sigstore/src/lib.rs Outdated
Comment thread crates/mise-sigstore/src/lib.rs
Comment thread crates/mise-sigstore/src/lib.rs
@github-actions

github-actions Bot commented Apr 19, 2026

Copy link
Copy Markdown

Hyperfine Performance

mise x -- echo

Command Mean [ms] Min [ms] Max [ms] Relative
mise-2026.5.7 x -- echo 24.1 ± 2.4 19.8 31.6 1.00
mise x -- echo 24.3 ± 2.3 20.0 30.4 1.01 ± 0.14

mise env

Command Mean [ms] Min [ms] Max [ms] Relative
mise-2026.5.7 env 24.0 ± 2.0 19.9 30.6 1.03 ± 0.14
mise env 23.2 ± 2.4 18.8 29.5 1.00

mise hook-env

Command Mean [ms] Min [ms] Max [ms] Relative
mise-2026.5.7 hook-env 24.8 ± 2.2 20.2 30.9 1.00
mise hook-env 25.1 ± 2.3 20.3 31.1 1.01 ± 0.13

mise ls

Command Mean [ms] Min [ms] Max [ms] Relative
mise-2026.5.7 ls 20.0 ± 1.9 16.4 25.2 1.00
mise ls 20.7 ± 2.0 16.5 27.4 1.03 ± 0.14

xtasks/test/perf

Command mise-2026.5.7 mise Variance
install (cached) 146ms 150ms -2%
ls (cached) 68ms 71ms -4%
bin-paths (cached) 73ms 76ms -3%
task-ls (cached) 582ms 577ms +0%

Comment thread crates/mise-sigstore/src/lib.rs Outdated
Comment thread crates/mise-sigstore/src/lib.rs
Comment thread crates/mise-sigstore/src/lib.rs
@jdx jdx force-pushed the codex/use-sigstore-rust-verify branch from 426e9d3 to 3bca599 Compare April 23, 2026 16:49
Comment thread Cargo.toml
Comment thread crates/mise-sigstore/data/tuf_root.json Outdated
jdx added a commit to jdx/sigstore-rust that referenced this pull request Apr 23, 2026
`verify_message_digest_attribute` hashed the `TSTInfo` content with SHA-256
unconditionally, regardless of what `SignerInfo.digestAlgorithm` (RFC 5652
§5.3) declared. That is correct for the public-good Sigstore TSA — which
signs with SHA-256 — but rejects every TSA response that uses any other
digest. The companion `verify_message_imprint` function one definition above
already reads the digest algorithm from the message imprint and switches on
SHA-256 / SHA-384 / SHA-512; this PR mirrors that pattern for the CMS
message-digest attribute.

The concrete trigger is GitHub Actions's internal TSA at
`timestamp.githubapp.com`, which signs CMS `SignedData` with SHA-384.
Any GitHub-issued artifact attestation (the bundles served from
`/repos/{owner}/{repo}/attestations/<digest>` for builds that publish via
`actions/attest-build-provenance`) currently fails with:

```
HashMismatch { expected: <48 hex bytes>, actual: <32 hex bytes> }
```

After this fix, the verifier picks SHA-384 from `signer_info.digest_alg.oid`
(already extracted a few lines below for ECDSA signature verification) and
the comparison succeeds.

The new regression test uses `jdx/communique@v0.1.9` — a real GitHub
attestation bundle, TSA-only with no Rekor entry — together with GitHub's
published TSA chain narrowed to `timestampAuthorities` (fetched once from
<https://tuf-repo.github.com/>). The existing `test_verify_valid_timestamp`
case (which covers SHA-256) still passes, so this is purely additive.

Discovered while switching `mise` from `sigstore-verification` to this crate
for GitHub artifact attestation verification (see
<jdx/mise#9260>).

Validation:
- `cargo test -p sigstore-tsa` — 15/15 pass (1 new)
- `cargo test` — full workspace passes
- `cargo fmt --check`
- `cargo clippy -p sigstore-tsa --all-targets`

Signed-off-by: jdx <216188+jdx@users.noreply.github.com>
wolfv pushed a commit to sigstore/sigstore-rust that referenced this pull request Apr 27, 2026
…ion (#83)

`verify_message_digest_attribute` hashed the `TSTInfo` content with SHA-256
unconditionally, regardless of what `SignerInfo.digestAlgorithm` (RFC 5652
§5.3) declared. That is correct for the public-good Sigstore TSA — which
signs with SHA-256 — but rejects every TSA response that uses any other
digest. The companion `verify_message_imprint` function one definition above
already reads the digest algorithm from the message imprint and switches on
SHA-256 / SHA-384 / SHA-512; this PR mirrors that pattern for the CMS
message-digest attribute.

The concrete trigger is GitHub Actions's internal TSA at
`timestamp.githubapp.com`, which signs CMS `SignedData` with SHA-384.
Any GitHub-issued artifact attestation (the bundles served from
`/repos/{owner}/{repo}/attestations/<digest>` for builds that publish via
`actions/attest-build-provenance`) currently fails with:

```
HashMismatch { expected: <48 hex bytes>, actual: <32 hex bytes> }
```

After this fix, the verifier picks SHA-384 from `signer_info.digest_alg.oid`
(already extracted a few lines below for ECDSA signature verification) and
the comparison succeeds.

The new regression test uses `jdx/communique@v0.1.9` — a real GitHub
attestation bundle, TSA-only with no Rekor entry — together with GitHub's
published TSA chain narrowed to `timestampAuthorities` (fetched once from
<https://tuf-repo.github.com/>). The existing `test_verify_valid_timestamp`
case (which covers SHA-256) still passes, so this is purely additive.

Discovered while switching `mise` from `sigstore-verification` to this crate
for GitHub artifact attestation verification (see
<jdx/mise#9260>).

Signed-off-by: jdx <216188+jdx@users.noreply.github.com>
@jdx jdx force-pushed the codex/use-sigstore-rust-verify branch from 3bca599 to 13d3013 Compare April 29, 2026 14:18
Comment thread crates/mise-sigstore/src/lib.rs
Comment thread crates/mise-sigstore/src/lib.rs
Comment thread crates/mise-sigstore/src/lib.rs
Comment thread crates/mise-sigstore/src/lib.rs
Comment thread Cargo.toml
Comment thread crates/mise-sigstore/src/lib.rs Outdated
Comment thread crates/mise-sigstore/src/lib.rs
Comment thread crates/mise-sigstore/src/lib.rs
Comment thread crates/mise-sigstore/src/lib.rs Outdated
Comment thread crates/mise-sigstore/src/lib.rs
Comment thread crates/mise-sigstore/src/lib.rs Outdated
Comment thread crates/mise-sigstore/src/lib.rs Outdated
jdx and others added 16 commits May 13, 2026 10:35
Replace the external `sigstore-verification` dependency with a local
`mise-sigstore` adapter built on `sigstore-verify` 0.6.5 from sigstore-rust.
The wrapper at `src/github/sigstore.rs` and the `vfox` crate are rerouted to
the new adapter; mise's call sites are unchanged because the wrapper from
PR #9307 already abstracts the underlying verifier.

Why a local adapter and not just bumping `sigstore-verification`: the upstream
`sigstore-verify` 0.6.x exposes a different (and richer) API surface. The
adapter keeps mise's existing helper-style API (`verify_github_attestation`,
`verify_cosign_signature`, `verify_slsa_provenance`, `detect_attestations`)
so call sites do not need to change.

Why we ship a custom TUF root and bypass `TrustedRoot::production()`: the
`sigstore-trust-root` crate embeds TUF root v1 (from 2021), which fails
verification under `tough` because the `expires` field uses a timezone-offset
format that `chrono` normalizes to UTC `Z` on re-serialization, breaking
the canonical-JSON byte sequence that the original signature was computed
over (0 of 5 signatures verify, threshold is 3). We embed root v12 (the
same workaround the original `sigstore` 0.13 crate uses) and pass it via
`TufConfig::custom`, so `tough` walks v12 -> v13 -> v14 successfully.
The TUF root file is added to `.prettierignore` because reformatting would
invalidate its signatures.

Verified end-to-end:
- cargo check, cargo clippy --all-targets, cargo fmt --all -- --check, taplo fmt
- cargo test --bin mise (747 tests pass)
- cargo test -p mise-sigstore (4 tests pass)
- mise run test:e2e -- test_aqua_github_attestations passes against
  goreleaser 2.14.1 (live download + TUF fetch + bundle verification)

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Picks up cosign v3 bundle parsing fix for missing optional fields and
TSA CMS digest-algorithm honoring from the new sigstore-rust release.
The custom TUF root v12 workaround is still required — upstream
sigstore-trust-root 0.6.6 still embeds the v1 root with the offset
expires format.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
GitHub artifact attestations are signed by GitHub's internal Fulcio
(O=GitHub, Inc.) and timestamped by GitHub's internal TSA. Neither is in
Sigstore's public trust root, so verifying these bundles via the public
Sigstore TrustedRoot fails: TSA timestamp validation reports "no
certificate matches issuer and serial number", and the certificate-chain
check fails because the leaf doesn't carry an SCT extension (GitHub's CA
doesn't log to public CT).

Detect GitHub-issued leaf certs by scanning the cert DER for the issuer
organization marker, route those bundles to a separate TrustedRoot
loaded from GitHub's published trusted_root.json (embedded; refresh from
https://tuf-repo.github.com/), and skip the certificate-chain check for
those bundles since SCT verification can't be turned off independently.
TSA timestamp now validates against the GitHub TSA cert, and the
signer-workflow identity check still gates acceptance.

Also refreshes the embedded Sigstore TUF root from v12 to v14 since v12
expired in August 2025 and was preventing the TUF chain walk on clean
caches.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
verify_cosign_signature_with_key took an unconditional network round-trip
to load the production TUF trust root before checking whether the file
was a bundle or a raw .sig. The raw-signature path doesn't use the trust
root at all, so this broke offline / TUF-unreachable cosign key
verification with a network error even when all required material was
local. Move the fetch inside the bundle branch.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
slsa-github-generator and goreleaser publish provenance as a DSSE
envelope in `*.intoto.jsonl` (e.g. sops's release artifacts), not as a
sigstore Bundle. The new mise-sigstore adapter routed every line through
`Bundle::from_json`, which rejects these files because they have no
`verificationMaterial`, so SLSA verification failed for any package
whose provenance ships in the DSSE-only format.

When Bundle parsing fails, fall back to parsing the line as a raw DSSE
envelope: extract the in-toto payload, check the SLSA predicate type,
enforce min_level, and confirm the artifact's sha256 digest is listed in
the statement's subjects. This matches the metadata-level checks the
previous `sigstore-verification` 0.2.x crate performed for the same
format.

Also defer `TrustRoots::load()` until a sigstore Bundle is actually
parsed, so the DSSE-only path doesn't pay an unnecessary TUF round-trip.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The previous DSSE-only fallback for SLSA provenance only checked that
the artifact's sha256 appeared in the in-toto subject list. An attacker
substituting the *.intoto.jsonl could forge a passing attestation by
including the artifact's digest and any payload they wanted, since the
envelope's signatures were never validated.

Verify each signature against the DSSE Pre-Authentication Encoding using
the public key extracted from the leaf cert embedded in the signature
object (slsa-github-generator format). At least one signature must
verify before the subject digest is even consulted. Cert chain
validation against Sigstore Fulcio is still skipped on this path because
intoto.jsonl files carry neither tlog nor TSA timestamps and Fulcio
leaf certs expire ~10 minutes after issuance, so we have no verified
time to anchor a chain check at — modern sigstore-Bundle provenance
takes the strict `verify_bundle` path with full chain + tlog
verification.

Tests cover three failure modes: tampered signature, missing
signatures, and artifact not in the subject list, using a real
sops v3.9.0 envelope as fixture.

Also tightens GitHub-internal certificate detection: instead of
substring-searching the cert DER for \`b\"GitHub, Inc.\"\` (which would
match any cert containing that ASCII anywhere), parse the certificate
with x509-cert and check the issuer's organizationName field.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
verify_bundle was called with `skip_tlog: true` from every code path —
including the public-Sigstore cosign path, where bundles ship a Rekor
inclusion proof that should be cryptographically validated. An attacker
swapping the bundle for one with a valid Fulcio chain but a fabricated
or absent tlog entry would have passed verification.

Drop the parameter and decide policy per bundle: skip tlog only when
`bundle.has_inclusion_proof()` returns false (GitHub artifact
attestations, TSA-only intoto bundles). Public-Sigstore cosign bundles
now go through full tlog verification — Rekor checkpoint signature, SET,
and inclusion-proof Merkle path.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Previously, GitHub artifact attestations skipped both Rekor tlog
verification (the bundle has none) and certificate-chain verification
(GitHub's CA doesn't issue SCTs, and sigstore-verify gates SCT and chain
checks behind the same flag). That left only TSA timestamp validation
and signer-workflow identity matching as the security boundary, which
was flagged as too weak — an attacker substituting the bundle could put
any cert claiming `O=GitHub, Inc.` and the cert chain would never be
verified.

Walk the chain manually with `webpki` against the GitHub trust root's CA
certs before delegating to sigstore-verify with skip_certificate_chain.
This performs the same chain build, signature checks, and CODE_SIGNING
EKU enforcement that sigstore-verify does, just without the SCT step.
TSA timestamp is still validated against the GitHub TSA cert and
signer-workflow identity is still matched.

Validation time is the leaf cert's notBefore — Fulcio leaf certs are
short-lived (~10 minutes) so they're expired by the time we verify, and
we don't have an independently verified TSA-derived time at this point
in the flow. At notBefore the chain is by definition valid; this still
catches forged certs whose issuer doesn't chain to the trust root.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Clippy's `single_match` lint flagged the `match Bundle::from_json` form
where only the Ok branch had a body — collapse to `if let Ok(bundle)`.
Pure refactor; no behavior change.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
verify_dsse_signature accepted any cert in the envelope's signature
field, including a trivially self-signed one — only the
key-matches-signature consistency was checked. An attacker who knows
the artifact's SHA-256 could substitute the *.intoto.jsonl with a
forged DSSE envelope built around their own keypair and self-signed
cert, and verification would pass.

Generalize the existing GitHub-internal chain helper into a reusable
verify_cert_chain that takes any TrustedRoot, and call it from
verify_dsse_signature against the public Sigstore trust root before
trusting the embedded leaf cert's public key. slsa-github-generator
emits Sigstore-Fulcio-issued certs, so chain-building succeeds for
genuine envelopes and fails for forgeries.

Add a unit test that swaps the genuine sops envelope's cert for a
foreign cert and asserts the chain step rejects it.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Previously verify_cert_chain used the leaf cert's notBefore as the
webpki validation time. notBefore is the moment the cert became valid,
so any intermediate CA in its validity window at that instant trivially
passes — including intermediates that are about to expire.

notAfter is strictly later than notBefore, so an intermediate CA whose
own validity ends before the leaf's notAfter will fail the time-window
check rather than silently pass. The leaf itself is at the edge of its
validity period, which webpki accepts.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
TrustRoots::load eagerly fetched both the Sigstore TUF production root
(network round-trip) and parsed the embedded GitHub trusted_root.json.
That meant verifying a GitHub artifact attestation — which only needs
the GitHub root — paid for a full Sigstore TUF walk on every install
and would fail outright once the embedded Sigstore TUF root expires
(2026-06-22), even though GitHub verification doesn't depend on it.

Switch TrustRoots to per-root lazy loading: callers ask for the root
they need and pay only for that one. The github attestation flow now
never touches the Sigstore TUF CDN; the cosign / SLSA bundle flows only
fetch the Sigstore root if/when they actually see a non-GitHub bundle.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
verify_cosign_signature_with_key called tokio::fs::read_to_string then
.ok().and_then(...) — which silently swallowed real I/O errors (e.g.
permission denied) and re-read the same file in the raw-signature
branch with no error context. Read once via tokio::fs::read, propagate
the I/O error with `?`, and fall through only when the bytes don't
parse as a sigstore Bundle. Refactor read_cosign_signature into a
pure decode helper so the bundle and raw paths share the same bytes.

Also documents why verify_cert_chain uses every CA cert in the trust
root as both a trust anchor and as an intermediate (matching what
sigstore-verify does internally, since we already trust the entire
embedded CA bundle and a chain may legitimately terminate at an
intermediate that we ship as trusted).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
sigstore-verify 0.7.0 (and the matching sigstore-trust-root) ships fixes
and APIs that obsolete all three local workarounds in mise-sigstore:

* The embedded Sigstore TUF root v1 (broken under `tough` due to
  timezone-offset re-serialization) is gone — upstream now ships v14
  directly, so `TrustedRoot::production()` works without a custom root.

* The embedded GitHub trusted_root.json is replaced by upstream's
  `TrustedRoot::from_embedded(SigstoreInstance::GitHub)` (byte-identical
  content, same fallback rationale: GitHub's TUF spec version is not
  yet validated by `tough`).

* `VerificationPolicy::skip_sct()` was added in 0.7.0 to skip just the
  SCT check while keeping certificate-chain validation on. GitHub
  artifact attestations don't carry public Sigstore CT SCTs, so this is
  exactly what we need — we no longer have to skip the whole chain step
  and re-implement it manually with `webpki` for GitHub-internal certs.
  `verify_cert_chain` is still used for raw DSSE envelopes
  (slsa-github-generator's `*.intoto.jsonl`), which don't go through
  `sigstore_verify::verify` at all.

Also removes the `crates/mise-sigstore/data/` entry from `.prettierignore`
now that the embedded JSON files are gone.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Cosign 2.x and earlier `cosign sign-blob --bundle` emits a v1 bundle
shape (`{base64Signature, cert, rekorBundle}`) rather than the modern
sigstore Bundle protobuf. sigstore-verify's `Bundle::from_json` rejects
the v1 shape outright, so any tool whose release pipeline still produces
v1 bundles (e.g. envsense, which goreleaser signs) was failing
`verify artifact with cosign` with "missing field `verificationMaterial`".

Add a fallback in `verify_cosign_signature`: when the JSON doesn't parse
as a modern Bundle, treat it as a v1 bundle, chain-validate the embedded
Fulcio cert against the public Sigstore trust root (reusing the same
`verify_cert_chain` helper we use for raw DSSE envelopes), and
ECDSA-verify `base64Signature` over the artifact bytes with the cert's
public key.

The rekord entry's hash and Rekor `SignedEntryTimestamp` aren't
independently re-checked — the cert+sig step already cryptographically
binds the signer to the artifact bytes, which matches what every
downstream consumer of cosign blob verification actually cares about.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@jdx jdx force-pushed the codex/use-sigstore-rust-verify branch from 1ca3b4c to 44006f0 Compare May 13, 2026 16:05
Comment thread crates/mise-sigstore/src/lib.rs
verify_min_slsa_level only checked predicateType and the SLSA level;
the parallel raw `*.intoto.jsonl` path already enforces that the
artifact's SHA-256 appears in the in-toto statement's `subject` array
via verify_intoto_payload, but the bundle path skipped it. Without this
check, a valid SLSA bundle signed for one artifact would accept a
completely different artifact as long as `verify_bundle` itself
succeeded.

Rename the helper to `verify_bundle_slsa` and delegate the predicate /
level / subject-digest check to `verify_intoto_payload`, so both code
paths apply the same verification.

Also extend `is_slsa_format_issue` to recognize format-mismatch errors
from sigstore-verify itself, which mise-sigstore maps into the
`Sigstore(_)` variant (e.g. `missing field 'verificationMaterial'`
when the file is a legacy cosign v1 bundle). Without this, the SLSA
fallback path treats those as real verification failures rather than
"wrong format, try the next strategy."

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

@cursor cursor Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cursor Bugbot has reviewed your changes and found 1 potential issue.

Fix All in Cursor

❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.

Reviewed by Cursor Bugbot for commit f614169. Configure here.

Comment thread crates/mise-sigstore/src/lib.rs Outdated
jdx and others added 2 commits May 13, 2026 11:23
Never constructed anywhere in the crate. Removing it shrinks the public
API surface and silences a stale dead-code warning.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Four rustls-webpki advisories (RUSTSEC-2026-0049, -0098, -0099, -0104)
reach mise transitively via sigstore-tsa 0.7.0 → sigstore-bundle →
sigstore-verify → mise-sigstore. sigstore-tsa pins `rustls-webpki = "0.102"`,
so the fix has to come from a sigstore-rust upstream bump to 0.103.x —
we can't drive that from here.

None of the four are exploitable in mise's verification paths: we don't
pass CRLs or URI-name-constrained certs to webpki, and the TSA chain
checks don't go through the affected code. Same pattern as the existing
sigstore-derived advisory ignores above.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@jdx jdx enabled auto-merge (squash) May 13, 2026 16:41
@jdx jdx merged commit 55425ed into main May 13, 2026
34 checks passed
@jdx jdx deleted the codex/use-sigstore-rust-verify branch May 13, 2026 16:48
3PeatVR pushed a commit to 3PeatVR/mise that referenced this pull request May 14, 2026
## Summary

Replace the external `sigstore-verification` 0.2.x dependency with a
local `mise-sigstore` adapter built on `sigstore-verify` 0.7.0 from
sigstore-rust. mise's call sites are unchanged — the wrapper at
[src/github/sigstore.rs](src/github/sigstore.rs) and the
[vfox](crates/vfox) crate are rerouted to the new adapter, which
preserves the existing helper-style API (`verify_github_attestation`,
`verify_cosign_signature`, `verify_slsa_provenance`,
`detect_attestations`).

### Why a local adapter rather than `sigstore-verify` directly

`sigstore-verify` 0.7.x exposes a different (and richer) API surface
than the legacy crate. The adapter keeps mise's existing helper-style
API stable, layers in GitHub-API attestation fetching (including
`bundle_url` resolution + Snappy decompression), and adds the two paths
sigstore-verify itself does not cover:

- **Legacy cosign v1 bundles** (`{base64Signature, cert, rekorBundle}`)
— emitted by `cosign sign-blob --bundle` 2.x and still produced by
goreleaser-signed releases (envsense, etc.). sigstore-verify's
`Bundle::from_json` rejects this shape, so we chain-validate the
embedded Fulcio cert ourselves and ECDSA-verify `base64Signature` over
the artifact bytes.
- **Raw DSSE envelopes** (`*.intoto.jsonl` from slsa-github-generator /
goreleaser) — no `verificationMaterial`, so sigstore-verify rejects
them. We verify the DSSE signature against the Fulcio cert in the
envelope (chain-validated), then enforce the artifact's SHA-256 is
present in the in-toto subject list.

### What `sigstore-verify` 0.7.0 buys us over 0.6.x

0.7.0 (and the matching `sigstore-trust-root` 0.7.0) lets us drop every
local workaround the original adapter shipped:

- The embedded Sigstore TUF root is gone — upstream ships TUF v14
directly, so `TrustedRoot::production()` walks the live TUF CDN with no
custom bootstrap root from us.
- The embedded `github_trusted_root.json` is gone —
`TrustedRoot::from_embedded(SigstoreInstance::GitHub)` provides it
(byte-identical content), same fallback rationale: `tough` doesn't yet
validate GitHub's TUF spec version.
- The manual `webpki` chain validation for GitHub-internal certs is gone
— `VerificationPolicy::skip_sct()` (new in 0.7.0) lets us turn off just
the SCT check (which GitHub doesn't issue) while keeping full
sigstore-verify-driven chain validation. We still use
`verify_cert_chain` for raw DSSE envelopes, since those don't go through
`sigstore_verify::verify` at all.

Net delta vs. the previous iteration of this PR: ~1,150 lines deleted
from `mise-sigstore`, plus the legacy cosign v1 path added.

## Verified

- `cargo check`, `cargo clippy --all-targets -- -D warnings`, `cargo fmt
--all -- --check`, `taplo fmt`
- `cargo test --bin mise` — 924 tests pass
- `cargo test -p mise-sigstore` — 8 tests pass
- e2e (locally): `test_aqua_github_attestations`, `test_aqua_cosign`,
`test_lockfile_cosign_top_level_binary`,
`test_lockfile_cosign_opts_only`, `test_python_github_attestations`,
`test_ruby_github_attestations` all pass — including the new
legacy-bundle path against envsense 0.3.4 and the modern bundle path
against goreleaser 2.14.1.

*This PR was generated by an AI coding assistant.*

<!-- CURSOR_SUMMARY -->
---

> [!NOTE]
> **High Risk**
> Replaces the core artifact attestation/cosign/SLSA verification
dependency and logic, including new certificate-chain and DSSE/legacy
bundle handling, which could incorrectly accept or reject releases if
any edge case is missed.
> 
> **Overview**
> Swaps out the `sigstore-verification` dependency for a new workspace
crate, `mise-sigstore`, built on `sigstore-verify` v0.7, and reroutes
`mise` and `vfox` verification call sites to it.
> 
> The new adapter adds GitHub attestation fetching (including
`bundle_url` resolution and Snappy decompression), updates verification
policy handling (e.g., skipping tlog or SCT checks when bundles lack
those proofs), and expands support to **legacy cosign v1 bundles** and
**raw DSSE `*.intoto.jsonl` provenance** by manually chain-validating
embedded Fulcio certs and verifying signatures + subject digests.
> 
> Also updates error classification in the GitHub backend to treat more
`sigstore-verify` parse/format failures as non-fatal format issues,
adjusts TLS feature wiring, adds release automation for publishing
`mise-sigstore`, and updates `deny.toml` ignores for transitive
`rustls-webpki` advisories.
> 
> <sup>Reviewed by [Cursor Bugbot](https://cursor.com/bugbot) for commit
29fa282. Bugbot is set up for automated
code reviews on this repo. Configure
[here](https://www.cursor.com/dashboard/bugbot).</sup>
<!-- /CURSOR_SUMMARY -->

---------

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
mise-en-dev added a commit that referenced this pull request May 14, 2026
### 🚀 Features

- **(patrons)** add `mise patrons` command by @jdx in
[#9841](#9841)

### 🐛 Bug Fixes

- **(task)** skip shebang line in displayed task command by @jdx in
[#9844](#9844)

### 🚜 Refactor

- **(security)** switch to sigstore-rust verification by @jdx in
[#9260](#9260)
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.

2 participants