Skip to content

security(lock): block github tool upgrade when provenance is lost#8706

Merged
jdx merged 13 commits intomainfrom
fix/provenance-version-upgrade
Mar 22, 2026
Merged

security(lock): block github tool upgrade when provenance is lost#8706
jdx merged 13 commits intomainfrom
fix/provenance-version-upgrade

Conversation

@jdx
Copy link
Copy Markdown
Owner

@jdx jdx commented Mar 22, 2026

Summary

  • When a github backend tool is upgraded and the new version lacks provenance verification that a prior version had, error and block the operation as a potential supply chain attack.
  • The check runs in both mise lock and the auto-lock path (during mise install/mise use), and hard-fails in both cases.
  • Scoped to github backend only — aqua (registry-driven) and vfox (plugin-driven) have different trust models where provenance changes across versions are expected.

Key design decisions

  • Preserves old lockfile entry — regressing tools are excluded from merge/save, so the old provenance-verified entry stays in the lockfile. The error persists on retry until the user resolves it.
  • Checks current platform only — new LockfileTool entries only have lock_platforms for the platform where installation ran; checking other platforms would cause false positives.
  • Filters both old and new entries by backend — avoids false positives when a tool migrates from github: to another backend (e.g., aqua:).
  • Compares against highest prior version — avoids spurious errors if an intermediate version without provenance was already accepted.
  • Defers errors across lockfiles — all lockfiles in a monorepo are processed and saved before the error is returned, so unaffected lockfiles aren't skipped.
  • Collects all errors — all provenance regressions are reported together instead of only the last one.
  • Error includes prior version — e.g. github:jdx/usage@1.6.0 has no provenance verification on linux-x64, but github:jdx/usage@1.5.0 had github-attestations

Test plan

  • mise run test:e2e test_lockfile_provenance_upgrade — verifies mise lock errors when provenance is lost (2s)
  • Manually verified no error for aqua backend tools

🤖 Generated with Claude Code


Note

Medium Risk
Changes lockfile update and auto-lock flows to hard-fail on certain GitHub tool upgrades, which can block installs/locks and may surface in monorepos; logic is new and touches core lockfile write paths.

Overview
Adds provenance-regression detection for github: backend tools: if upgrading to a newer version would remove previously-recorded provenance for the current platform, the operation is blocked with an error that cites the prior verified version.

Applies this check in both mise lock (per-resolution apply) and the install-time auto-lock/update paths, while deferring errors until after lockfiles are written so unaffected entries/lockfiles are still saved. Includes a new E2E test (test_lockfile_provenance_upgrade) asserting mise lock fails with a supply-chain warning when provenance is lost.

Written by Cursor Bugbot for commit 7801020. This will update automatically on new commits. Configure here.

@gemini-code-assist
Copy link
Copy Markdown
Contributor

Summary of Changes

Hello, 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 enhances supply chain security by introducing a critical warning system within the mise tool. It detects and alerts users if a GitHub-backed tool, previously installed with provenance verification, is updated to a version that lacks such verification. This proactive measure aims to mitigate risks associated with potential supply chain attacks by prompting users to verify the authenticity of new releases.

Highlights

  • Provenance Warning for GitHub Tools: Implemented a warning mechanism that triggers when a GitHub backend tool is upgraded, and the new version lacks provenance verification while a prior version had it. This helps detect potential supply chain attacks.
  • Scope Limitation: The provenance check is specifically applied to GitHub backend tools, as other backends like aqua and vfox have different trust models where provenance changes are expected.
  • End-to-End Testing: Added a new end-to-end test to verify that the warning is correctly emitted when a GitHub tool is upgraded from a version with provenance to one without.
Using Gemini Code Assist

The 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 /gemini <command> or @gemini-code-assist <command>. Below is a summary of the supported commands on the current page.

Feature Command Description
Code Review /gemini review Performs a code review for the current pull request in its current state.
Pull Request Summary /gemini summary Provides a summary of the current pull request in its current state.
Comment @gemini-code-assist Responds in comments when explicitly tagged, both in pull request comments and review comments.
Help /gemini help Displays a list of available commands.

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 .gemini/ folder in the base of the repository. Detailed instructions can be found here.

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.

Footnotes

  1. Review the Privacy Notices, Generative AI Prohibited Use Policy, Terms of Service, and learn how to configure Gemini Code Assist in GitHub here. Gemini can make mistakes, so double check it and use code with caution.

@greptile-apps
Copy link
Copy Markdown
Contributor

greptile-apps bot commented Mar 22, 2026

Greptile Summary

This PR adds supply-chain protection to mise's lockfile infrastructure: when a github: backend tool is upgraded and the new version has no provenance verification where a prior version did, the operation now hard-fails rather than silently writing the unverified entry.

Key changes:

  • New check_single_tool_provenance helper and check_provenance_regression function in lockfile.rs implement the core regression detection, scoped to github: backend only and guarded against false-positives from downgrades (version comparison gate) and backend migrations (backend filter on prior entries).
  • apply_lock_result now returns Result<()> and performs a second provenance check during platform resolution.
  • Both the mise lock CLI path and the auto-lock path (mise install/mise use via update_lockfiles) accumulate errors and defer them until after all lockfiles are saved, so unaffected tools' entries are preserved.
  • config/mod.rs upgrades the previously-silent warn! in the auto-lock call-site to a hard-fail ? propagation.
  • Previous reviewer concerns around monorepo multi-lockfile skipping, only-last-error reporting, downgrade false-positives, and missing backend filters on prior entries have all been resolved in this revision.

Remaining concern:
The apply_lock_result guard inside auto_lock_new_versions is effectively unreachable in normal flows: update_lockfiles runs first and its merge_tool_entries removes old version entries before auto_lock_new_versions reads the lockfile, leaving no prior version to compare against. The real mise install/mise use protection is provided entirely by check_provenance_regression inside update_lockfiles.

Confidence Score: 4/5

  • PR is safe to merge; the security guard works correctly for the primary mise lock path and the update_lockfiles path covers mise install/mise use.
  • All previously raised concerns (monorepo skipping, single-error reporting, downgrade false-positives, missing backend filter on prior entries, silent discard in auto-lock) have been resolved. Two minor issues remain: a UX conflation of provenance-blocked entries with "skipped" in the summary output (P2), and an effectively unreachable second regression check inside auto_lock_new_versions that could create false confidence that mise install is doubly protected when it is not (P1 design/documentation concern). Neither blocks correctness of the primary protection paths.
  • src/lockfile.rs — specifically the interaction between update_lockfiles/merge_tool_entries and the apply_lock_result check inside auto_lock_new_versions

Important Files Changed

Filename Overview
src/lockfile.rs Core provenance-regression logic added: check_single_tool_provenance (shared helper), check_provenance_regression (for update_lockfiles), and apply_lock_result now returns Result. Errors are properly deferred so all lockfiles are saved before the error surfaces. However, the apply_lock_result guard inside auto_lock_new_versions is effectively a no-op in normal flows because update_lockfiles already removes old version entries before auto_lock_new_versions reads the lockfile.
src/cli/lock.rs Correctly collects provenance errors into all_provenance_errors across lockfiles and defers the final error until all lockfiles are written. Minor UX issue: provenance-blocked entries are counted as "skipped" in the progress summary alongside legitimately-skipped entries.
src/config/mod.rs Auto-lock path upgraded from silent warn! to hard-fail ? propagation for both update_lockfiles and auto_lock_new_versions. The wrap_err context is informative. The ? on update_lockfiles means provenance errors there correctly prevent auto_lock_new_versions from running.
e2e/lockfile/test_lockfile_provenance_upgrade New e2e test that injects synthetic provenance into a lockfile entry, upgrades the version, and asserts mise lock fails with "supply chain" in the output. The test relies on specific GitHub releases existing and is documented as such. The $PLATFORM quoting in the final assert_fail_contains call is correctly handled with backslash-escaped inner quotes.

Flowchart

%%{init: {'theme': 'neutral'}}%%
flowchart TD
    A[mise install / mise use] --> B[rebuild_shims_and_runtime_symlinks]
    B --> C[update_lockfiles]
    C --> D{check_provenance_regression\nfor each tool on current platform}
    D -- regression detected --> E[skip merge for regressing tools\ncollect error]
    D -- no regression --> F[merge_tool_entries\nwrites new version, drops old]
    E --> G[save lockfile\nold entry preserved]
    F --> G
    G --> H{any provenance errors?}
    H -- yes --> I[return Err\nauto_lock_new_versions skipped]
    H -- no --> J[auto_lock_new_versions]
    J --> K[resolve platform checksums/URLs]
    K --> L[apply_lock_result\ncheck_single_tool_provenance]
    L -- regression detected --> M[collect error\ndo NOT write platform info]
    L -- no regression --> N[set_platform_info in lockfile]
    M --> O[save lockfile]
    N --> O
    O --> P{any provenance errors?}
    P -- yes --> Q[return Err to caller]
    P -- no --> R[OK]

    S[mise lock] --> T[process_tools\nper tool×platform in JoinSet]
    T --> U[resolve_tool_lock_info]
    U --> V[apply_lock_result\ncheck_single_tool_provenance]
    V -- regression --> W[collect provenance_error\nresult = false]
    V -- ok --> X[set_platform_info]
    W --> Y[lockfile.write]
    X --> Y
    Y --> Z{any provenance errors?}
    Z -- yes --> AA[return Err after all lockfiles written]
    Z -- no --> AB[OK]
Loading

Comments Outside Diff (2)

  1. src/cli/lock.rs, line 479-483 (link)

    Provenance-blocked entries counted as "skipped" in summary

    When apply_lock_result errors due to a provenance regression, the result is pushed with false at line 480. The summary at lines 149-150 computes skipped = results.len() - successful, which groups provenance-blocked entries together with legitimately skipped entries (e.g. already up-to-date). The user then sees ✓ Updated N platform entries (M skipped) with no indication of why M entries were not updated — the supply-chain error only appears afterward.

    Consider tracking provenance errors separately so the skipped count only covers intentional skips:

    if let Err(e) = lockfile::apply_lock_result(lockfile, resolution) {
        provenance_errors.push(e.to_string());
        // Do NOT push to results; track separately so the skipped count
        // reflects only legitimately-skipped (already up-to-date) entries.
    } else {
        results.push((short, platform_key, ok));
    }

    Then adjust the summary to report provenance-blocked entries explicitly (e.g. N blocked).

  2. src/lockfile.rs, line 1142-1152 (link)

    Regression check in auto_lock_new_versions can't fire after update_lockfiles

    auto_lock_new_versions is always called after update_lockfiles (see config/mod.rs). By the time auto_lock_new_versions reads the lockfile from disk, update_lockfiles has already replaced the old version entry with the new one via merge_tool_entries — which only keeps versions present in entries, dropping older versions that were only in existing_tools. Therefore lockfile.tools.get(&short) inside apply_lock_result contains only the new version, not the prior version that had provenance, so the filter t.version != version leaves nothing to compare against and check_single_tool_provenance always returns None.

    This means the provenance regression guard in auto_lock_new_versions is silently a no-op in the normal mise install/mise use flow. The protection for that path relies entirely on the check_provenance_regression call inside update_lockfiles. If update_lockfiles misses a regression (e.g., when the new LockfileTool from installation has an empty platforms map and new_provenance is None because the tool hadn't been fully resolved yet), auto_lock_new_versions will not catch it.

    Consider whether the second check is providing actual coverage, or whether the comment/docs should be updated to clarify that auto_lock_new_versions coverage is intentionally left to update_lockfiles.

Fix All in Claude Code

Reviews (11): Last reviewed commit: "fix(lock): count provenance-blocked entr..." | Re-trigger Greptile

@jdx jdx changed the title security(install): warn when github tool loses provenance on upgrade security(install): block github tool upgrade when provenance is lost Mar 22, 2026
Copy link
Copy Markdown
Contributor

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

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 adds an important security feature: it warns the user when a tool upgrade results in a loss of provenance, which could indicate a supply chain attack. The implementation is correct and includes a new end-to-end test to verify the functionality. I have one suggestion to improve the code's readability and make it more idiomatic.

src/lockfile.rs Outdated
Comment on lines +1112 to +1120
// publishes a release without attestations.
if info.provenance.is_none() && backend.starts_with("github:") {
if let Some(tools) = lockfile.tools.get(&short) {
let prior_provenance = tools.iter().find_map(|t| {
if t.version != version {
t.platforms
.get(&platform_key)
.and_then(|pi| pi.provenance.as_ref())
} else {
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.

medium

This logic for finding a prior version with provenance can be made more concise and idiomatic by chaining iterator methods. Using filter() before find_map() separates the concerns of filtering out the current version and finding the desired provenance, which improves readability.

                let prior_provenance = tools
                    .iter()
                    .filter(|t| t.version != version)
                    .find_map(|t| t.platforms.get(&platform_key)?.provenance.as_ref());

@jdx jdx changed the title security(install): block github tool upgrade when provenance is lost security(lock): block github tool upgrade when provenance is lost Mar 22, 2026
@jdx jdx force-pushed the fix/provenance-version-upgrade branch from 3bb24a5 to 43b365d Compare March 22, 2026 15:38
@jdx jdx force-pushed the fix/provenance-version-upgrade branch from 231d4d3 to 2095e12 Compare March 22, 2026 15:58
jdx and others added 10 commits March 22, 2026 16:14
…upgrade

When locking a new version of a github backend tool, check if any prior
version in the lockfile had provenance verification. If the new version
has no provenance, warn about a potential supply chain attack (attacker
publishes a release without attestations).

This check is scoped to github backend only — aqua and vfox tools have
different trust models where provenance changes across versions are
expected.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
… on upgrade

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The ? on apply_lock_result caused early return from the result loop,
silently dropping already-applied lock entries for unaffected tools
because lockfile.save() was never reached. Now collect the provenance
error, save the lockfile for all unaffected tools, then return the error.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Move provenance check into update_lockfiles where old version entries
  are still available (fixes dead code in auto_lock_new_versions path
  where merge_tool_entries already removed old entries)
- Defer provenance errors across the outer lockfile loop so monorepo
  projects don't skip remaining lockfiles
- Compare against highest prior version only, not any arbitrary version
- Include prior version number in error message
- Quote $PLATFORM in test, add version dependency comment

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Auto-lock path now hard-fails on provenance regression instead of
  warn (config/mod.rs propagates error instead of swallowing it)
- check_provenance_regression only checks current platform to avoid
  false positives (new entries only have lock_platforms for current
  platform, not all 5 common platforms)
- Collect all provenance errors instead of only reporting the last one
  (both mise lock and auto-lock paths)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Skip regressing tools during merge/save so the old provenance-verified
  entry is preserved in the lockfile (prevents single-warning bypass)
- Filter new entries by github: backend to avoid false positives when
  a tool migrates from github to another backend (e.g., aqua)
- Refactor check_provenance_regression to return regressing tool names

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Only flag upgrades that lose provenance — intentional downgrades to
versions that predate attestation support are allowed. Applied to both
check_provenance_regression and apply_lock_result paths.

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

- Auto-lock provenance errors are now collected across all lockfiles
  before returning, so monorepo projects don't skip remaining lockfiles
- apply_lock_result now filters prior entries by github: backend,
  matching check_provenance_regression's behavior (avoids false
  positives when a tool previously appeared under aqua/vfox with
  provenance)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@jdx jdx force-pushed the fix/provenance-version-upgrade branch from 2201981 to c7116fc Compare March 22, 2026 16:14
…ce check

None backend on new LockfileTool entries bypassed the github-only guard
because `if let Some` didn't match. Use `is_some_and` to skip entries
that aren't explicitly github backend.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@jdx jdx force-pushed the fix/provenance-version-upgrade branch from cad9cb5 to ae6fdcd Compare March 22, 2026 16:35
- mise lock now saves the lockfile before returning provenance errors,
  matching update_lockfiles and auto_lock_new_versions behavior
- Provenance errors are deferred across all lockfile targets in monorepo
- Extracted shared check_single_tool_provenance helper to deduplicate
  the provenance regression logic between apply_lock_result and
  check_provenance_regression

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Copy link
Copy Markdown

@cursor cursor bot left a comment

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.

Provenance-blocked entries are now included in the results as skipped
so the summary counts add up to total_tasks.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@github-actions
Copy link
Copy Markdown

Hyperfine Performance

mise x -- echo

Command Mean [ms] Min [ms] Max [ms] Relative
mise-2026.3.10 x -- echo 24.2 ± 0.9 23.4 34.8 1.00
mise x -- echo 24.4 ± 0.8 23.5 32.9 1.01 ± 0.05

mise env

Command Mean [ms] Min [ms] Max [ms] Relative
mise-2026.3.10 env 24.5 ± 0.9 23.1 30.7 1.00
mise env 25.5 ± 1.0 23.7 34.1 1.04 ± 0.06

mise hook-env

Command Mean [ms] Min [ms] Max [ms] Relative
mise-2026.3.10 hook-env 26.0 ± 1.3 23.8 33.2 1.00
mise hook-env 26.9 ± 0.9 24.3 31.1 1.03 ± 0.06

mise ls

Command Mean [ms] Min [ms] Max [ms] Relative
mise-2026.3.10 ls 25.4 ± 0.9 23.1 30.2 1.00
mise ls 26.3 ± 1.2 24.1 35.3 1.04 ± 0.06

xtasks/test/perf

Command mise-2026.3.10 mise Variance
install (cached) 159ms 158ms +0%
ls (cached) 89ms 89ms +0%
bin-paths (cached) 92ms 92ms +0%
task-ls (cached) 891ms 860ms +3%

@jdx jdx enabled auto-merge (squash) March 22, 2026 18:22
@jdx jdx disabled auto-merge March 22, 2026 18:42
@jdx jdx merged commit a4c7fb0 into main Mar 22, 2026
34 of 36 checks passed
@jdx jdx deleted the fix/provenance-version-upgrade branch March 22, 2026 18:42
jdx pushed a commit that referenced this pull request Mar 22, 2026
### 🐛 Bug Fixes

- **(completions)** update zsh completions for usage v3.1.0 by @jdx in
[#8715](#8715)

### Security

- **(lock)** block github tool upgrade when provenance is lost by @jdx
in [#8706](#8706)
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