fix(install): prevent --locked mode from modifying or bypassing lockfile#8362
fix(install): prevent --locked mode from modifying or bypassing lockfile#8362
Conversation
Summary of ChangesHello @jdx, 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 the 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
|
There was a problem hiding this comment.
Code Review
This pull request correctly implements the described fixes to prevent --locked mode from modifying or bypassing the lockfile. The changes in lock.rs, use.rs, and tool_version.rs are logical and address the specified scenarios. The existing suggestion to improve the consistency of error handling in locked mode is valid and has been retained, as it does not conflict with any established rules. Overall, the changes are solid.
src/toolset/tool_version.rs
Outdated
| if Settings::get().locked && request.source().path().is_some() { | ||
| bail!( | ||
| "{}@{} is not in the lockfile\nhint: Run `mise install` without --locked to update the lockfile", | ||
| request.ba().short, | ||
| request.version() | ||
| ); | ||
| } |
There was a problem hiding this comment.
While the current logic is correct for config-sourced tools, it could be improved for consistency. Argument-sourced tools that are missing from the lockfile currently fall through to remote resolution and fail later inside backend.install_version with a less specific error message (No lockfile URL found...).
To provide a more consistent and earlier failure for all tool requests that should be in the lockfile, you could change this condition to check the ToolRequest type instead of the source. This would catch missing tools from any source (arguments, environment, etc.) at this point with a clear error message, and correctly ignore path: and system versions which are not expected in the lockfile.
This would unify the error handling for locked mode and prevent an unnecessary remote resolution attempt for argument-sourced tools.
| if Settings::get().locked && request.source().path().is_some() { | |
| bail!( | |
| "{}@{} is not in the lockfile\nhint: Run `mise install` without --locked to update the lockfile", | |
| request.ba().short, | |
| request.version() | |
| ); | |
| } | |
| if Settings::get().locked && !matches!(&request, ToolRequest::Path { .. } | ToolRequest::System { .. }) { | |
| bail!( | |
| "{}@{} is not in the lockfile\nhint: Run `mise install` without --locked to update the lockfile", | |
| request.ba().short, | |
| request.version() | |
| ); | |
| } |
Greptile SummaryThis PR adds three critical enforcement checks to prevent the lockfile from being modified or bypassed when
The changes correctly implement the lockfile integrity guarantees for Confidence Score: 4/5
Important Files Changed
Last reviewed commit: 9030466 |
src/toolset/tool_version.rs
Outdated
| if Settings::get().locked && request.source().path().is_some() { | ||
| bail!( | ||
| "{}@{} is not in the lockfile\nhint: Run `mise install` without --locked to update the lockfile", | ||
| request.ba().short, | ||
| request.version() | ||
| ); | ||
| } |
There was a problem hiding this comment.
Should this check also exclude tools with linked versions (like the lockfile resolution check above)?
Tools with linked versions (user-created symlinks to system installations) are skipped in the lockfile resolution at line 52 with !has_linked_version(request.ba()). Since these tools are managed outside mise's lockfile system, they probably shouldn't be required to be in the lockfile during --locked mode.
Consider adding the same check here:
| if Settings::get().locked && request.source().path().is_some() { | |
| bail!( | |
| "{}@{} is not in the lockfile\nhint: Run `mise install` without --locked to update the lockfile", | |
| request.ba().short, | |
| request.version() | |
| ); | |
| } | |
| if Settings::get().locked && !has_linked_version(request.ba()) && request.source().path().is_some() { | |
| bail!( | |
| "{}@{} is not in the lockfile\nhint: Run `mise install` without --locked to update the lockfile", | |
| request.ba().short, | |
| request.version() | |
| ); | |
| } |
Hyperfine Performance
|
| Command | Mean [ms] | Min [ms] | Max [ms] | Relative |
|---|---|---|---|---|
mise-2026.2.21 x -- echo |
27.2 ± 0.3 | 26.7 | 31.7 | 1.00 |
mise x -- echo |
27.5 ± 0.8 | 26.8 | 40.3 | 1.01 ± 0.03 |
mise env
| Command | Mean [ms] | Min [ms] | Max [ms] | Relative |
|---|---|---|---|---|
mise-2026.2.21 env |
26.6 ± 0.9 | 26.0 | 41.4 | 1.00 |
mise env |
26.7 ± 0.2 | 26.2 | 27.8 | 1.01 ± 0.04 |
mise hook-env
| Command | Mean [ms] | Min [ms] | Max [ms] | Relative |
|---|---|---|---|---|
mise-2026.2.21 hook-env |
27.4 ± 0.4 | 26.8 | 31.0 | 1.00 |
mise hook-env |
27.5 ± 0.2 | 27.0 | 28.6 | 1.00 ± 0.02 |
mise ls
| Command | Mean [ms] | Min [ms] | Max [ms] | Relative |
|---|---|---|---|---|
mise-2026.2.21 ls |
25.6 ± 0.2 | 25.1 | 26.7 | 1.00 |
mise ls |
25.7 ± 0.2 | 25.2 | 26.8 | 1.01 ± 0.01 |
xtasks/test/perf
| Command | mise-2026.2.21 | mise | Variance |
|---|---|---|---|
| install (cached) | 168ms | 168ms | +0% |
| ls (cached) | 94ms | 94ms | +0% |
| bin-paths (cached) | 98ms | 97ms | +1% |
| task-ls (cached) | 824ms | -70% |
9030466 to
0324004
Compare
0324004 to
964ec72
Compare
In --locked mode, `mise lock` now refuses to run, tool resolution fails with a clear error when a config-sourced tool is missing from the lockfile, and `mise use @latest` no longer bypasses the lockfile. Closes #8305 Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
964ec72 to
57f5be6
Compare
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.
| EOF | ||
|
|
||
| assert_fail_contains "mise lock --locked 2>&1" "mise lock is disabled in --locked mode" | ||
| MISE_LOCKED=1 assert_fail_contains "mise lock 2>&1" "mise lock is disabled in --locked mode" |
There was a problem hiding this comment.
E2E test doesn't propagate MISE_LOCKED to subprocess
Medium Severity
MISE_LOCKED=1 assert_fail_contains "..." sets MISE_LOCKED as a shell variable via prefix assignment to a function call, but does NOT export it. Inside quiet_assert_fail, the command runs via bash -c "$1 2>&1" — a new process that only inherits exported environment variables. Since MISE_LOCKED is never exported, mise lock won't see it, won't enter locked mode, and won't produce the expected error message. The test will fail for the wrong reason or unexpectedly pass/fail.
…ile (jdx#8362) ## Summary - `mise lock` now refuses to run in `--locked` mode with a clear error and hint - Tool resolution in `--locked` mode fails with an error when a config-sourced tool is missing from the lockfile, instead of silently falling through to remote resolution - `mise use tool@latest` no longer bypasses the lockfile in `--locked` mode Closes jdx#8305 ## Test plan - [x] `mise run lint-fix` passes - [x] All 491 unit tests pass - [ ] `MISE_LOCKED=1 mise lock` should error with hint message - [ ] `mise install --locked` with a valid lockfile should not modify it - [ ] `mise install --locked` for a tool not in lockfile should error 🤖 Generated with [Claude Code](https://claude.com/claude-code) <!-- CURSOR_SUMMARY --> --- > [!NOTE] > **Medium Risk** > Behavior changes in `--locked` mode can break existing workflows that relied on implicit remote resolution or running `mise lock` while locked. Impact is limited to locked/lockfile paths and adds coverage via e2e tests. > > **Overview** > **Enforces strict `--locked` behavior around lockfiles.** `mise lock` now exits with an error (and hint) whenever `--locked`/`MISE_LOCKED=1` is active, preventing lockfile updates in locked mode. > > Tool version resolution now fails fast in locked mode when a config-sourced tool/version is missing from the lockfile instead of falling back to remote resolution, and `mise use tool@latest` no longer disables lockfile usage when locked. > > Adds an e2e test (`test_lockfile_locked_mode`) covering that `mise lock` is refused under locked mode. > > <sup>Written by [Cursor Bugbot](https://cursor.com/dashboard?tab=bugbot) for commit 57f5be6. This will update automatically on new commits. Configure [here](https://cursor.com/dashboard?tab=bugbot).</sup> <!-- /CURSOR_SUMMARY --> Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
### 🚀 Features - **(backend-plugin)** pass options to vfox backend plugins by @Attempt3035 in [#8369](#8369) ### 🐛 Bug Fixes - **(install)** prevent --locked mode from modifying or bypassing lockfile by @jdx in [#8362](#8362) - **(install)** clear aqua bin_paths cache after install to prevent stale PATH by @jdx in [#8374](#8374) - **(task)** prevent broken pipe panic and race condition in remote git task cache by @vmaleze in [#8375](#8375) ### 📦️ Dependency Updates - update docker/build-push-action digest to 10e90e3 by @renovate[bot] in [#8367](#8367) - update fedora:43 docker digest to 781b764 by @renovate[bot] in [#8368](#8368) ### 📦 Registry - add porter ([github:getporter/porter](https://github.com/getporter/porter)) by @lbergnehr in [#8380](#8380) - add entire ([aqua:entireio/cli](https://github.com/entireio/cli)) by @TyceHerrman in [#8378](#8378) - add topgrade ([aqua:topgrade-rs/topgrade](https://github.com/topgrade-rs/topgrade)) by @TyceHerrman in [#8377](#8377) ### Chore - remove pre-commit config and tool dependency by @jdx in [#8373](#8373) ### New Contributors - @Attempt3035 made their first contribution in [#8369](#8369) - @lbergnehr made their first contribution in [#8380](#8380)


Summary
mise locknow refuses to run in--lockedmode with a clear error and hint--lockedmode fails with an error when a config-sourced tool is missing from the lockfile, instead of silently falling through to remote resolutionmise use tool@latestno longer bypasses the lockfile in--lockedmodeCloses #8305
Test plan
mise run lint-fixpassesMISE_LOCKED=1 mise lockshould error with hint messagemise install --lockedwith a valid lockfile should not modify itmise install --lockedfor a tool not in lockfile should error🤖 Generated with Claude Code
Note
Medium Risk
Behavior changes in
--lockedmode can break existing workflows that relied on implicit remote resolution or runningmise lockwhile locked. Impact is limited to locked/lockfile paths and adds coverage via e2e tests.Overview
Enforces strict
--lockedbehavior around lockfiles.mise locknow exits with an error (and hint) whenever--locked/MISE_LOCKED=1is active, preventing lockfile updates in locked mode.Tool version resolution now fails fast in locked mode when a config-sourced tool/version is missing from the lockfile instead of falling back to remote resolution, and
mise use tool@latestno longer disables lockfile usage when locked.Adds an e2e test (
test_lockfile_locked_mode) covering thatmise lockis refused under locked mode.Written by Cursor Bugbot for commit 57f5be6. This will update automatically on new commits. Configure here.