feat: store provenance verification results in lockfile#8495
Conversation
Summary of ChangesHello, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed! This pull request significantly enhances the security of Highlights
Changelog
Activity
Using Gemini Code AssistThe full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips. Invoking Gemini You can request assistance from Gemini at any point by creating a comment using either
Customization To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a Limitations & Feedback Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for Github and other Google products, sign up here. You can also get AI-powered code generation, chat, as well as code reviews directly in the IDE at no cost with the Gemini Code Assist IDE Extension. Footnotes
|
Greptile SummaryThis PR implements provenance verification persistence in Verified findings:
Architecture summary:
The core security logic is well-structured and correctly prevents downgrades. The e2e test fixtures and sed portability concerns were verified and found to be non-issues in the current code. Confidence Score: 4/5
|
There was a problem hiding this comment.
Code Review
This pull request adds provenance verification result storage to the lockfile, which is a great security enhancement. When a tool's artifact is verified using sigstore/SLSA/GitHub attestations, this information is now recorded. Subsequent installations will fail if this recorded provenance cannot be re-verified, preventing potential downgrade or stripping attacks. The changes are well-implemented across the github and aqua backends, and the lockfile format has been updated accordingly with new tests. I have one minor suggestion to improve code robustness.
Note: Security Review did not run due to the size of the PR.
src/backend/aqua.rs
Outdated
| pi.provenance_url = Some( | ||
| provenance_path | ||
| .file_name() | ||
| .unwrap() |
There was a problem hiding this comment.
Using .unwrap() here could lead to a panic if provenance_path.file_name() returns None. While it's unlikely in this context, it's safer to handle this case gracefully to prevent unexpected crashes. Consider using ok_or_else or a match statement to provide a more informative error message if the filename cannot be extracted.
| .unwrap() | |
| .file_name().ok_or_else(|| eyre!("Could not get filename from provenance path: {:?}", provenance_path))? |
| .await | ||
| } else { | ||
| None | ||
| }; |
There was a problem hiding this comment.
Redundant API call per platform in provenance detection
Medium Severity
detect_provenance_type is called from resolve_lock_info_for_target once per platform target during mise lock. Each call fetches the same GitHub release via API (line 383-390). With ~11 platform targets, this produces ~11 duplicate API calls for the identical release data. The release and its assets are the same regardless of target platform, so this detection only needs to happen once.
9a0492c to
92cb984
Compare
|
Re: "Redundant API call per platform in provenance detection" — this is not an issue. This comment was generated by Claude Code. |
|
bugbot run |
|
bugbot run |
Hyperfine Performance
|
| Command | Mean [ms] | Min [ms] | Max [ms] | Relative |
|---|---|---|---|---|
mise-2026.3.4 x -- echo |
23.7 ± 0.6 | 22.6 | 25.7 | 1.00 ± 0.03 |
mise x -- echo |
23.6 ± 0.4 | 22.9 | 25.1 | 1.00 |
mise env
| Command | Mean [ms] | Min [ms] | Max [ms] | Relative |
|---|---|---|---|---|
mise-2026.3.4 env |
22.9 ± 0.8 | 22.1 | 29.0 | 1.01 ± 0.04 |
mise env |
22.8 ± 0.3 | 22.3 | 24.8 | 1.00 |
mise hook-env
| Command | Mean [ms] | Min [ms] | Max [ms] | Relative |
|---|---|---|---|---|
mise-2026.3.4 hook-env |
23.4 ± 0.3 | 22.8 | 25.2 | 1.00 |
mise hook-env |
23.5 ± 0.2 | 22.9 | 25.2 | 1.00 ± 0.02 |
mise ls
| Command | Mean [ms] | Min [ms] | Max [ms] | Relative |
|---|---|---|---|---|
mise-2026.3.4 ls |
22.6 ± 0.2 | 22.0 | 24.4 | 1.00 |
mise ls |
22.8 ± 0.5 | 22.2 | 28.9 | 1.01 ± 0.02 |
xtasks/test/perf
| Command | mise-2026.3.4 | mise | Variance |
|---|---|---|---|
| install (cached) | 149ms | 148ms | +0% |
| ls (cached) | 81ms | 81ms | +0% |
| bin-paths (cached) | 84ms | 84ms | +0% |
| task-ls (cached) | 809ms | 810ms | +0% |
src/lockfile.rs
Outdated
|
|
||
| // Merging empty with provenance picks up provenance from other | ||
| let merged = without.merge_with(&with_provenance); | ||
| assert_eq!(merged.provenance, Some("github-attestations".to_string())); |
There was a problem hiding this comment.
Test contradicts merge_with provenance implementation
Medium Severity
The test test_provenance_merge_preserves_existing asserts that without.merge_with(&with_provenance) returns Some("github-attestations"), but merge_with always uses self.provenance.clone() (line 131). Since without is a default PlatformInfo with provenance: None, the result will be None, causing this assertion to fail. Either the merge_with implementation needs to fall back to other.provenance (like it does for url), or the test expectation is wrong.
Additional Locations (1)
|
bugbot run |
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes and found 1 potential issue.
Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.
|
|
||
| # Inject provenance into the lockfile (simulating a previously-verified install) | ||
| ESCAPED_PLATFORM="${PLATFORM//./\\.}" | ||
| sed "/\"platforms\.$ESCAPED_PLATFORM\"\]/a provenance = \"github-attestations\"" mise.lock >mise.lock.tmp && mv mise.lock.tmp mise.lock |
There was a problem hiding this comment.
E2e test creates duplicate TOML keys causing parse failure
Medium Severity
Since github_attestations defaults to true (confirmed in settings.toml), mise lock at line 31 will already write provenance = "github-attestations" for jqlang/jq in the lockfile. The sed injection on line 37 then appends another provenance = "github-attestations" line after the platform section header, creating a duplicate TOML key. This makes the lockfile unparseable, so the subsequent mise install fails with a TOML parse error instead of the expected "downgrade attack" message, causing assert_fail_contains on line 46 to fail.
- Remove speculative GithubAttestations from detect_provenance_type since we cannot verify attestations exist without downloading the artifact. Recording is deferred to install-time after verification. - Remove dead provenance_url sort key from lockfile serializer - Improve downgrade error messages: show "no verification" instead of "None" when no provenance mechanism ran Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes and found 2 potential issues.
Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.
| } | ||
|
|
||
| None | ||
| } |
There was a problem hiding this comment.
Optimistic provenance detection causes false install failures
Medium Severity
detect_provenance_type records provenance in the lockfile based solely on aqua registry configuration (e.g., slsa_provenance field exists and is enabled), without verifying that the actual provenance artifacts are available. During install, verify_slsa can hit early-return paths (e.g., "no asset configured" or "no asset found") that warn! and return Ok(()) without recording provenance. The post-verification downgrade check then sees expected=SLSA but got=None and raises a misleading "downgrade attack" error, blocking installation of tools whose registry metadata is incomplete or whose provenance files don't actually exist.
Additional Locations (1)
| }; | ||
| HTTP.download_file(&url, &checksum_path, Some(ctx.pr.as_ref())) | ||
| .await?; | ||
| } |
There was a problem hiding this comment.
Cosign skip not applied to checksum download condition
Low Severity
When the lockfile specifies non-cosign provenance (e.g., SLSA), skip_cosign is true, but the checksum file download condition (needs_checksum || !skip_cosign) still downloads the checksum file if needs_checksum is true. This is mostly fine, but if needs_checksum is false and skip_cosign is true, the checksum file isn't downloaded, which is correct. However, the inverse case — when needs_checksum is false and skip_cosign is false (i.e., provenance is cosign) — the checksum file IS downloaded solely for cosign, but the checksum parse and set at line 1053-1061 is gated by needs_checksum, so no checksum is written. This logic works but makes the download condition harder to reason about because the two concerns (checksum and cosign) are conflated in one conditional.
…ixes - Remove speculative SLSA from detect_provenance_type — verify_slsa has early-return paths (missing asset, no config) that silently skip verification, causing false downgrade errors when the lockfile expects SLSA but verification never ran - Tighten minisign short-circuit to check is_slsa()/is_github_attestations() explicitly since cosign runs later and cannot be set at that point - Rename !skip_cosign to needs_cosign for clarity in download condition - Drain unconsumed map entries in ProvenanceType's visit_map to satisfy strict deserializers - Document intentional Ord/PartialEq inconsistency on ProvenanceType: Ord compares by priority ordinal only, not inner data Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes and found 2 potential issues.
Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.
| } | ||
|
|
||
| None | ||
| } |
There was a problem hiding this comment.
Aqua SLSA/attestation provenance not detected during lock
High Severity
detect_provenance_type in the aqua backend only returns Cosign or Minisign, never Slsa or GithubAttestations. Since mise lock only calls resolve_lock_info (which calls detect_provenance_type) and does NOT run the install/verify path, SLSA and GitHub attestation provenance is never recorded in the lockfile for aqua tools. The E2E test test_lockfile_provenance expects provenance.slsa for sops after mise lock, but sops has SLSA provenance (not cosign or minisign), so this assertion will fail. The repo's mise.lock also contains github-attestations provenance for aqua tools like actionlint/jq/gh, which cannot have been generated by this code path.
Additional Locations (1)
| fn cmp(&self, other: &Self) -> std::cmp::Ordering { | ||
| self.ordinal().cmp(&other.ordinal()) | ||
| } | ||
| } |
There was a problem hiding this comment.
Ord and PartialEq/Hash contract violation on ProvenanceType
Medium Severity
ProvenanceType derives PartialEq, Eq, and Hash (which compare all fields including the inner url of Slsa), but manually implements Ord using only ordinal() (ignoring inner data). This violates Rust's requirement that a.cmp(b) == Equal implies a == b. Two Slsa variants with different URLs are Equal under Ord but unequal under PartialEq/Hash. While documented with a comment and clippy allow, this can cause subtle bugs in any sorted collection and violates the standard library trait contract.
When a higher-priority mechanism (e.g., GithubAttestations) has already recorded provenance, skip cosign verification to prevent hard errors from cosign failures blocking a legitimately verified install. Also skips the unnecessary checksum file download in this case. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Re-add SLSA to detect_provenance_type (above cosign, below GithubAttestations) — SLSA registry config is reliable for detection unlike attestations which need API probing - Fix Ord/PartialEq/Hash contract: implement PartialEq, Eq, and Hash manually to compare by ordinal only, consistent with Ord. Two Slsa variants with different URLs are now considered equal under all comparison traits. Tests updated to use pattern matching for URL verification instead of assert_eq. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes and found 1 potential issue.
Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.
src/backend/github.rs
Outdated
| name.contains(".intoto.jsonl") | ||
| || name.ends_with(".sigstore.json") | ||
| || name.ends_with(".sigstore") | ||
| }); |
There was a problem hiding this comment.
SLSA detection heuristic mismatches actual verification asset selection
High Severity
detect_provenance_type treats .sigstore.json and .sigstore files as SLSA evidence and records Slsa provenance in the lockfile. However, pick_best_provenance (used by try_verify_slsa at install time) only matches .intoto.jsonl, provenance, and .attestation — it does not match .sigstore.json/.sigstore. This causes try_verify_slsa to find no provenance asset and return NoAttestations, triggering a false-positive "downgrade attack" error that blocks installation. The existing _security_features method (line 128-138) already correctly classifies .sigstore.json/.sigstore as GitHub Attestations, not SLSA.
detect_provenance_type was matching .sigstore.json/.sigstore files as SLSA evidence, but pick_best_provenance only matches .intoto.jsonl, provenance, and .attestation. This caused false-positive downgrade errors when .sigstore files were present but no SLSA provenance file existed. The .sigstore files are GitHub Attestations (already handled by the attestation API probe), not SLSA. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Note that ProvenanceType should not be used as a BTreeMap/HashSet key due to intentional ordinal-only equality, and that merge() should be preferred over max() to preserve inner data. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes and found 1 potential issue.
Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.
| let locked_provenance = tv | ||
| .lock_platforms | ||
| .get(&platform_key) | ||
| .and_then(|pi| pi.provenance.clone()); |
There was a problem hiding this comment.
GitHub backend doesn't clear lockfile provenance before verification
Medium Severity
In verify_attestations_or_slsa, locked_provenance is read via .clone() (preserving the original in lock_platforms), while the aqua backend uses .take() to clear it before re-verification. When provenance_result is Some(...), the caller unconditionally overwrites platform_info.provenance, which works. But if in the future a code path returns Ok(None) without hitting the downgrade check at line 1249 (e.g., if locked_provenance were None but provenance still existed in lock_platforms from a different source), stale provenance data could persist unverified.
Additional Locations (1)
…e vs take - Document why cosign_already_verified is safe to cache in aqua verify - Note ruby's speculative GithubAttestations assumption in doc comment - Explain github.rs clone vs aqua's take approach for locked_provenance Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes and found 1 potential issue.
Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.
| }); | ||
| if has_slsa { | ||
| return Some(ProvenanceType::Slsa { url: None }); | ||
| } |
There was a problem hiding this comment.
Lock-time SLSA detection ignores platform, install-time filters by platform
Medium Severity
detect_provenance_type at lock-time checks if any release asset matches SLSA patterns (.intoto.jsonl, provenance, .attestation) using release.assets.iter().any(...) without platform filtering. At install-time, try_verify_slsa uses picker.pick_best_provenance(&asset_names) which filters and scores by platform. A release may have a provenance file for linux but not for macos. Lock-time would record SLSA provenance for all platforms, but install-time would fail with NoAttestations on the platform lacking a matching provenance asset, producing a misleading downgrade-attack error.
Additional Locations (1)
- github.rs: detect_provenance_type now accepts PlatformTarget for
platform-aware SLSA asset detection via pick_best_provenance
- lockfile.rs: Slsa{url:None} serializes as plain string "slsa" instead
of orphaned table header [provenance.slsa]
- e2e: remove existing provenance lines before sed injection to avoid
duplicate TOML keys; update assertion to match new serialization
- aqua.rs: document SLSA vs attestations detection priority and stale
checksum trust assumptions
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
|
bugbot run |
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes and found 2 potential issues.
Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.
| let platform_key = self.get_platform_key(); | ||
| let platform_info = tv.lock_platforms.entry(platform_key).or_default(); | ||
| platform_info.provenance = Some(provenance_type); | ||
| } |
There was a problem hiding this comment.
GitHub backend doesn't clear stale provenance on successful verification
Medium Severity
The verify_attestations_or_slsa method reads locked_provenance via .clone() (since tv is &ToolVersion) and the comment claims "the caller unconditionally overwrites platform_info.provenance." But the caller only writes when provenance_result is Some. When verification succeeds and returns Some, it correctly overwrites. However, if a tool previously had provenance in the lockfile and on a subsequent install verify_attestations_or_slsa returns Ok(Some(...)) with a different type than what was locked, no downgrade check fires because the check only runs when no verification succeeded (Ok(None)). The locked_provenance value is read but never compared against the actual result when verification does succeed — a tool could silently switch from GithubAttestations to Slsa without triggering the downgrade detection.
Additional Locations (1)
| url_api: None, | ||
| conda_deps: None, | ||
| provenance: Some(ProvenanceType::Minisign), | ||
| ..Default::default() |
There was a problem hiding this comment.
Zig lockfile records minisign provenance before verification succeeds
Medium Severity
In resolve_lock_info, the JSON-fetched path pre-sets provenance: Some(ProvenanceType::Minisign) at lock time (line 433). But the fallback path for numbered versions intentionally omits provenance (line 438 comment). This means mise lock records minisign provenance for all zig versions that have JSON metadata, even though minisign verification hasn't actually run yet. If minisign verification later fails at install time, the lockfile already claims minisign was verified, creating a mismatch that blocks future installs with a false "downgrade attack" error.
Additional Locations (1)
| let legacy_provenance_url = match t.remove("provenance_url") { | ||
| Some(toml::Value::String(s)) => Some(s), | ||
| _ => None, | ||
| }; | ||
| let provenance = match t.remove("provenance") { | ||
| Some(toml::Value::String(s)) => { | ||
| let mut prov: ProvenanceType = s | ||
| .parse() | ||
| .map_err(|_| eyre!("unrecognized provenance type {s:?} in lockfile"))?; | ||
| // Attach legacy provenance_url to SLSA if present | ||
| if let ProvenanceType::Slsa { ref mut url } = prov { | ||
| *url = legacy_provenance_url; | ||
| } | ||
| Some(prov) |
There was a problem hiding this comment.
legacy_provenance_url is silently discarded when provenance is not Slsa
The backwards-compat migration reads provenance_url from the legacy lockfile field and then attaches it only to a Slsa provenance variant. If a legacy lockfile contains both provenance = "github-attestations" and provenance_url = "https://..." (an inconsistency that could arise from manual editing or a partially-migrated lockfile), the URL is silently discarded with no warning or parse error.
More importantly, if a lockfile was written with provenance = "slsa" (string, no URL) but also has a provenance_url key that this migration was designed to migrate, the url field will be set to legacy_provenance_url. However, if the new-format provenance = { slsa = { url = "..." } } was already written AND a stale provenance_url key also exists (orphaned from a previous format), the table-branch of the match at line 331 handles deserialization — but legacy_provenance_url is read first (line 316) and then never used. This is harmless but means the migration path has dead code when the new table format is present.
Consider adding a diagnostic warning when legacy_provenance_url is non-None but the provenance type doesn't accept it, to help users identify inconsistent lockfiles:
if legacy_provenance_url.is_some() && !matches!(provenance, Some(ProvenanceType::Slsa { .. })) {
warn!("lockfile has provenance_url but provenance type is not slsa — url discarded");
}- zig.rs: remove premature minisign provenance from resolve_lock_info; provenance is recorded in install_version_ after download() confirms minisign verification succeeded - github.rs: add defense-in-depth checks that verify the provenance type returned by successful verification matches the lockfile expectation, preventing silent type switches between mechanisms Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
### 🚀 Features - **(vfox)** add `RUNTIME.envType` for libc variant detection by @malept in [#8493](#8493) - store provenance verification results in lockfile by @jdx in [#8495](#8495) ### 🐛 Bug Fixes - **(env)** skip remote version fetching for "latest" in prefer-offline mode by @jdx in [#8500](#8500) - **(tasks)** deduplicate shared deps across task delegation by @vadimpiven in [#8497](#8497) - **(windows)** correctly identify mise binary without extension by @jdx in [#8503](#8503) ### 🚜 Refactor - **(core)** migrate cmd! callers to async with kill_on_drop by @jdx in [a63f7d2](a63f7d2) ### Chore - **(ci)** temporarily disable `mise up` in release-plz by @jdx in [#8504](#8504) - consolidate all linters into hk.pkl by @jdx in [#8498](#8498) ## 📦 Aqua Registry Updates #### New Packages (1) - [`apache/ant`](https://github.com/apache/ant)


Summary
Store provenance verification results in the lockfile so that subsequent installs can detect downgrade/stripping attacks. When a lockfile records that a tool was installed with a specific provenance mechanism (e.g., SLSA, GitHub attestations, minisign, cosign), future installs will refuse to proceed if that mechanism is disabled or unavailable.
Key changes
ProvenanceTypeenum with ordered variants:Minisign < Cosign < Slsa < GithubAttestationsstrum::Displayandstrum::EnumIsfor ergonomic variant checksSlsavariant carries an optional URL for the.intoto.jsonlprovenance fileprovenance = { slsa = { url = "..." } }, others as plain stringsOrdimpl ensuresmax()always preserves the highest-priority provenanceAqua backend (
src/backend/aqua.rs):mise lock)GitHub backend (
src/backend/github.rs):verify_attestations_or_slsareturnsOption<ProvenanceType>for recordingmise lockvia attestation API probeCore plugins (zig, ruby):
Lockfile (
src/lockfile.rs):merge_withusesstd::cmp::max()to prevent provenance downgradeprovenance_urlfield intoSlsa { url }set_platform_infopreserves highest-priority provenance across updatesE2E test (
e2e/lockfile/test_lockfile_provenance):Test plan
cargo checkpassesmise run lint)mise run test:e2e test_lockfile_provenancepasses🤖 Generated with Claude Code
Note
High Risk
Touches core install/verification logic and changes lockfile serialization format; mistakes could block installs or weaken/overly-tighten supply-chain verification behavior.
Overview
mise locknow records which provenance mechanism (e.g.,github-attestations,slsa,cosign,minisign) was used per tool+platform inmise.lock, and subsequent installs fail if that expected verification is disabled/unavailable (downgrade attack protection).This introduces a new
provenancefield andProvenanceTypein the lockfile model (including SLSA URL support + legacy compat) and updates backends/plugins (notablyaqua,github,rubyprecompiled, andzig) to detect, write, and strictly validate provenance during verification; the lockfile writer also changes platform entries from inline tables to dotted-key subtables. Adds new e2e coverage for provenance writing/enforcement and updates the generatedmise.lockaccordingly.Written by Cursor Bugbot for commit 196d502. This will update automatically on new commits. Configure here.