Skip to content

fix(shim): prevent recursion when directory env is filtered - #10982

Merged
jdx merged 4 commits into
jdx:mainfrom
risu729:codex-20260714-220704-fda5e7
Jul 15, 2026
Merged

jdx merged 4 commits into
jdx:mainfrom
risu729:codex-20260714-220704-fda5e7

Conversation

@risu729

@risu729 risu729 commented Jul 14, 2026 •

Copy link
Copy Markdown
Contributor

Summary

  • make each shim identify itself by passing its absolute path to mise
  • reject only the active shim during executable lookup instead of relying exclusively on reconstructed shim directories
  • retain the existing configured user and system shim-directory filtering
  • preserve legitimate executables that share a directory with a shim
  • retain the active-shim identity across Unix --deny-env lookup, then remove the internal marker before starting the real tool
  • cover native, file, hardlink, same-directory sibling, and filtered-environment cases on Unix and Windows

Motivation

Before this change, mise primarily avoided shim recursion by reconstructing its user and system shim directories from the current process environment. That works when the mise process sees the same directory configuration that was used to create or expose the shim.

It is not sufficient whenever a shim remains reachable through PATH but the process that mise starts with has different directory context. Examples include:

  • MISE_SHIMS_DIR, MISE_DATA_DIR, XDG_DATA_HOME, or MISE_SYSTEM_DATA_DIR being filtered or changed
  • a shim created by one mise installation invoking another mise installation
  • copied, relocated, stale, or otherwise externally exposed shims
  • a shim directory being present through a path alias or symlink
  • GUI applications, IDEs, CI jobs, or other non-interactive processes inheriting an OS-level PATH

This does not require normal shell activation. mise activate normally adds the selected tools' real bin directories to PATH, but shims are also used independently through mise activate --shims, direct OS-level PATH configuration, IDE/GUI integration, and CI. An existing shim entry can also be retained when not_found_auto_install is enabled.

In the failure case, the active shim delegates to mise x -- <tool>. If mise cannot recognize that shim from its reconstructed directories, executable lookup can select the same shim again and recurse.

This is therefore a general shim identity issue rather than behavior specific to any one parent application.

Implementation

Each native or generated shim sets an internal __MISE_SHIM_PATH marker to its own absolute path before delegating to mise. Mise then:

  1. captures the marker before --deny-env can remove process variables
  2. keeps filtering the configured user and system shim directories
  3. enumerates executable candidates and skips the exact active shim
  4. rejects a direct attempt to execute that same shim recursively
  5. removes the marker before launching the selected real executable

The active shim's parent directory is not excluded wholesale. A file shim such as tool.cmd can legitimately coexist with a real tool.exe in the same directory, so filtering is based on file identity rather than directory membership.

The marker is internal dispatch state; the change does not preserve arbitrary MISE_* variables through --deny-env.

Validation

The particular parent-application environment was not reproduced locally. Validation models the general failure condition directly by placing an active shim outside mise's reconstructed shim directories and filtering its directory configuration.

  • mise run lint-fix
  • mise run format
  • cargo test --all-features --no-run
  • mise run test:e2e cli/test_exec_shim_recursion cli/test_system_shims_no_recursion cli/test_shims_fallback
  • Windows CI coverage for native, file, hardlink, and same-directory sibling executables

Summary by CodeRabbit

  • Bug Fixes
    • Prevented recursive shim invocation across native, file-based, and hardlink shims.
    • Improved executable resolution to skip active shims and run the intended real tool.
    • Preserved correct behavior when environment variables are filtered or shim paths are outside configured directories.
    • Added clearer errors when no suitable executable can be found.

@coderabbitai

coderabbitai Bot commented Jul 14, 2026 •

Copy link
Copy Markdown
Contributor

Review Change Stack

📝 Walkthrough

Walkthrough

The change adds __MISE_SHIM_PATH tracking and recursive invocation guards across native, generated, Unix, and Windows shim execution. PATH resolution excludes mise shim directories and active shim candidates, with regression coverage for multiple Windows and Unix shim forms.

Changes

Shim recursion prevention

Layer / File(s) Summary
Active shim path contract
src/env.rs, src/file.rs, crates/mise-shim/src/main.rs
Defines the active shim environment contract and helpers for path comparison, configured shim-directory detection, and active shim identification.
Shim entrypoint guards
src/shims.rs, crates/mise-shim/src/main.rs
Native and generated shims derive their paths, reject recursive invocation, and propagate __MISE_SHIM_PATH when delegating to mise.
Executable resolution guards
src/cli/exec.rs, src/shims.rs
Unix and Windows lookup excludes mise shim directories and active shim candidates, preserves shim state through environment filtering, and clears the marker before execution.
Recursion regression coverage
e2e/cli/test_exec_shim_recursion, e2e-win/shim_recursion.Tests.ps1
Covers external active shims, --deny-env, native shims, file shims, hardlinks, sibling executables, and shim-path leakage.

Estimated code review effort: 3 (Moderate) | ~25 minutes

Sequence Diagram(s)

sequenceDiagram
  participant Shim
  participant mise
  participant exec_program
  participant which
  participant RealTool
  Shim->>mise: delegate with __MISE_SHIM_PATH
  mise->>exec_program: resolve requested tool
  exec_program->>which: search PATH without mise shim directories
  which-->>exec_program: return executable candidates
  exec_program->>RealTool: execute non-active shim candidate
Loading

Possibly related PRs

  • jdx/mise#10421: Both modify Windows shim scripts and recursion handling.

Poem

I’m a rabbit guarding the shim-path gate,
No recursive hops shall seal your fate.
PATH skips shim burrows tucked inside,
Real tools now safely bound outside.
Windows tests thump their feet: hooray!

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly matches the main change: preventing shim recursion when environment markers are filtered.

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@greptile-apps

greptile-apps Bot commented Jul 14, 2026 •

Copy link
Copy Markdown
Contributor

Greptile Summary

This PR refines the shim recursion guard from directory-level exclusion to per-candidate exclusion, so legitimate executables that share a directory with an active shim are no longer hidden. It records the active shim's absolute path in __MISE_SHIM_PATH and forces the corresponding Lazy static to initialize before any --deny-env pass can clear the variable, then strips the marker from the child's environment before exec.

  • src/env.rs adds MISE_SHIM_PATH (a Lazy<RwLock<Option<PathBuf>>> read from __MISE_SHIM_PATH at startup); src/file.rs adds is_active_mise_shim for canonicalized per-file comparison; exec.rs switches to which_in_all + find(|c| !is_active_mise_shim(c)) with appropriate shim-context error handling.
  • Bash, cmd, and native (mise-shim) shim scripts each gain a self-recursion guard that sets __MISE_SHIM_PATH before delegating to mise x, mirroring the Rust-side check; the Windows hardlink path additionally detects recursion inside handle_shim via invoked_shim_path.
  • New Unix and Windows e2e tests cover the same-directory sibling case, the deny-env marker-retention case, and all three Windows shim modes (file, native, hardlink).

Confidence Score: 5/5

Safe to merge. The change is narrowly scoped to recursion detection, all three shim modes are covered, and the deny-env edge case is handled by forcing Lazy initialization before the env-clearing pass.

The core logic — per-candidate filtering via is_active_mise_shim, the drop(MISE_SHIM_PATH.read()) initialization guard before deny-env processing, and clearing the marker before exec — is consistent across Unix and Windows paths and is exercised by new e2e tests. No incorrect behavior was identified.

No files require special attention.

Important Files Changed

Filename Overview
src/cli/exec.rs Switches from which_in to which_in_all to iterate all candidates and skip only the exact active shim; adds a deny-env initialization guard via drop(MISE_SHIM_PATH.read()) and removes the shim marker from the env before exec. Logic is correct.
src/file.rs Adds is_active_mise_shim helper (candidate-level, canonicalized comparison); refactors is_mise_shims_dir doc. Both helpers are straightforward and correctly use canonicalize_or_self + paths_eq.
src/env.rs Adds MISE_SHIM_PATH_ENV constant and MISE_SHIM_PATH lazy RwLock static initialized from the env at first access. Clean addition; Windows path also writes to the static directly.
src/shims.rs Windows hardlink path gets recursion detection in handle_shim via invoked_shim_path; bash shim script gets a recursion guard + marker export before exec mise x; cmd shim gets an equivalent guard. which_shim fallback now uses is_active_mise_shim per-candidate instead of excluding the whole parent dir.
crates/mise-shim/src/main.rs Native shim binary gains a paths_eq helper (lexical + canonicalized fallback) and recursion detection; passes __MISE_SHIM_PATH to the mise subprocess. The dual-comparison strategy (raw first, canonical only when both succeed) correctly avoids asymmetric UNC comparisons.
e2e/cli/test_exec_shim_recursion Adds Unix regression tests for same-directory sibling executables and deny-env marker retention; new test scenarios are well-structured and target the fixed code paths.
e2e-win/shim_recursion.Tests.ps1 Adds Windows Pester tests for native shim, file shim, and hardlink shim across the filtered-MISE_DATA_DIR and same-directory-sibling scenarios. PATH and MISE_DATA_DIR are properly saved/restored in finally blocks.

Reviews (4): Last reviewed commit: "test(shim): exercise canonical active sh..." | Re-trigger Greptile

Comment thread src/shims.rs Resolved
Comment thread crates/mise-shim/src/main.rs Resolved

@coderabbitai coderabbitai 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.

🧹 Nitpick comments (1)
src/file.rs (1)

895-916: 🚀 Performance & Scalability | 🔵 Trivial | ⚡ Quick win

Avoid allocating a new PathBuf for the active shim directory.

Because is_mise_shims_dir is called in a loop for every entry in the PATH during executable resolution, we can avoid cloning and allocating a new PathBuf on each iteration by using .as_deref() and keeping active_shims as an Option<&Path>.

♻️ Proposed refactor
-    let active_shims = env::MISE_SHIM_PATH
-        .as_ref()
-        .cloned()
-        .and_then(|shim| shim.parent().map(Path::to_path_buf));
+    let active_shims = env::MISE_SHIM_PATH
+        .as_deref()
+        .and_then(Path::parent);
     if paths_eq(&resolved, &dirs::SHIMS)
         || paths_eq(&resolved, &sys_shims)
         || active_shims
-            .as_ref()
             .is_some_and(|active| paths_eq(&resolved, active))
     {
         return true;
     }
     let canon_input = canonicalize_or_self(&resolved);
     let canon_user = canonicalize_or_self(&dirs::SHIMS);
     let canon_sys = canonicalize_or_self(&sys_shims);
-    let canon_active = active_shims.as_deref().map(canonicalize_or_self);
+    let canon_active = active_shims.map(canonicalize_or_self);
     paths_eq(&canon_input, &canon_user)
         || paths_eq(&canon_input, &canon_sys)
         || canon_active
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/file.rs` around lines 895 - 916, Update the active shim directory
handling in is_mise_shims_dir to keep active_shims as an Option<&Path> by using
as_deref() on env::MISE_SHIM_PATH, avoiding cloned PathBuf allocation on each
PATH iteration. Preserve the existing direct and canonicalized comparisons for
the active directory.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Nitpick comments:
In `@src/file.rs`:
- Around line 895-916: Update the active shim directory handling in
is_mise_shims_dir to keep active_shims as an Option<&Path> by using as_deref()
on env::MISE_SHIM_PATH, avoiding cloned PathBuf allocation on each PATH
iteration. Preserve the existing direct and canonicalized comparisons for the
active directory.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Central YAML (base), Organization UI (inherited)

Review profile: CHILL

Plan: Pro Plus

Run ID: ff31bc43-90b3-4160-944a-a7c77d6409b0

📥 Commits

Reviewing files that changed from the base of the PR and between e392013 and 7af693d.

📒 Files selected for processing (6)
  • crates/mise-shim/src/main.rs
  • e2e-win/shim_recursion.Tests.ps1
  • src/cli/exec.rs
  • src/env.rs
  • src/file.rs
  • src/shims.rs

@risu729
risu729 force-pushed the codex-20260714-220704-fda5e7 branch from 7af693d to 85aa8ca Compare July 14, 2026 22:30

@coderabbitai coderabbitai 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.

🧹 Nitpick comments (1)
e2e/cli/test_exec_shim_recursion (1)

31-54: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Optionally create the mock shim file on disk to fully mirror the comment's scenario.

The comment rightly mentions that "File shims can coexist with a real executable...". While mise accurately uses the candidate path identity (and thus passing the string to __MISE_SHIM_PATH is enough to test the path-matching logic), actually creating an empty siblingtool.cmd on disk makes the directory state 100% true to reality. It ensures the file goes through the happy path of canonicalization rather than relying on the non-existent file string fallback.

💡 Proposed tweak
 echo SAME_DIRECTORY_REAL_TOOL
 TOOL
 chmod +x "$mixed_dir/siblingtool"
+touch "$mixed_dir/siblingtool.cmd"
 export PATH="$mixed_dir:$PATH"
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@e2e/cli/test_exec_shim_recursion` around lines 31 - 54, Optionally create an
empty mock shim file at "$mixed_dir/siblingtool.cmd" before the PATH setup in
the test, so the candidate path used by __MISE_SHIM_PATH exists and exercises
canonicalization while preserving the existing siblingtool assertions.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Nitpick comments:
In `@e2e/cli/test_exec_shim_recursion`:
- Around line 31-54: Optionally create an empty mock shim file at
"$mixed_dir/siblingtool.cmd" before the PATH setup in the test, so the candidate
path used by __MISE_SHIM_PATH exists and exercises canonicalization while
preserving the existing siblingtool assertions.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Central YAML (base), Organization UI (inherited)

Review profile: CHILL

Plan: Pro Plus

Run ID: 7ed7dad5-47d7-445f-88af-4ed277b3f643

📥 Commits

Reviewing files that changed from the base of the PR and between 85aa8ca and cd47c9f.

📒 Files selected for processing (5)
  • e2e-win/shim_recursion.Tests.ps1
  • e2e/cli/test_exec_shim_recursion
  • src/cli/exec.rs
  • src/file.rs
  • src/shims.rs
🚧 Files skipped from review as they are similar to previous changes (2)
  • src/cli/exec.rs
  • src/shims.rs

@risu729
risu729 marked this pull request as ready for review July 15, 2026 15:32
@jdx
jdx merged commit 4a94f29 into jdx:main Jul 15, 2026
33 checks passed
@risu729
risu729 deleted the codex-20260714-220704-fda5e7 branch July 16, 2026 03:56
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