fix(backend): strip mise shims from dependency_env PATH to prevent fork bomb#8475
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 resolves a critical bug that leads to infinite process spawning in 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 fixes a critical fork-bomb bug where Root cause correctly identified: In Fix is minimal and correct: The new path-stripping logic in Broad coverage: The fix applies to all backends using the default Test is well-designed: The e2e test uses the portable Confidence Score: 5/5
Last reviewed commit: 19fe4ed |
There was a problem hiding this comment.
Code Review
This pull request aims to address a critical fork bomb issue that occurs when using a go: backend tool with an uninstalled go version by stripping the mise shims directory from the PATH within dependency_env to prevent infinite recursion of mise exec calls. While the fix is effective for Unix-like systems and validated by the new e2e/backend/test_go_shim_recursion test, the path comparison used for filtering is fragile on Windows. This fragility could allow the fork bomb to persist on Windows, leading to a Denial of Service (DoS). Additionally, there is a minor suggestion for a small performance improvement.
|
Thanks for the reviews! Greptile / portability: Fixed in cbdc866 — switched from Gemini / Windows path comparison: Also fixed in cbdc866 — the filter now uses the same case-insensitive, separator-normalised comparison with This comment was generated by Claude Code. |
|
Sorry, it seems I broke the code by accepting the greptile suggestion. Fixing it. |
0b4838f to
2afb4bc
Compare
…rk bomb When a go: backend tool is configured alongside a go version that is not installed, and the remote version cache has been cleared, _list_remote_versions calls dependency_env to build the environment for `go list`. In --shims mode, PRISTINE_ENV contains the mise shims directory in PATH (since shims are added to the shell PATH without setting __MISE_DIFF). With go not installed, the only `go` on PATH is its shim, which calls `mise exec`, which resolves all tools in config including the go: backend, which calls _list_remote_versions again — an infinite fork bomb until OOM. Fix by stripping dirs::SHIMS from the PATH in dependency_env, so the shim cannot be invoked. The dependency tool either resolves to a real installed binary or fails cleanly. This affects all backends that use the default dependency_env (go, npm, gem, dotnet, spm, elixir). Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> Co-authored-by: greptile-apps[bot] <165735046+greptile-apps[bot]@users.noreply.github.com>
2afb4bc to
19fe4ed
Compare
### 🐛 Bug Fixes - **(activate)** reorder shims to front of PATH on re-source in fish by @jdx in [#8534](#8534) - **(backend)** strip mise shims from dependency_env PATH to prevent fork bomb by @pose in [#8475](#8475) - **(github)** resolve "latest" version correctly via GitHub API by @jdx in [#8532](#8532) - **(lock)** set env tags and clarify lockfile docs by @jdx in [#8519](#8519) - **(lock)** use separate mise.<env>.lock files instead of env tags by @jdx in [#8523](#8523) - **(task)** include args in task output prefix and truncate long prefixes by @jdx in [#8533](#8533) - **(task)** only include args in task prefix when disambiguating duplicates by @jdx in [#8536](#8536) - **(test)** pin goreleaser version in attestation e2e test by @jdx in [#8518](#8518) - **(windows)** env._.source needs to run bash.exe on Windows (fix #6513) by @pjeby in [#8520](#8520) - handle locked .exe shims on Windows during reshim by @davireis in [#8517](#8517) ### 🚜 Refactor - **(prepare)** remove touch_outputs and update docs to reflect blake3 hashing by @jdx in [#8535](#8535) ### 📚 Documentation - **(docker)** replace jdxcode/mise image with curl install, update to debian:13-slim by @jdx in [#8526](#8526) - fix "gzip: stdin is encrypted" error in shell tricks cookbook by @pjeby in [#8512](#8512) ### 📦 Registry - add tigerbeetle ([github:tigerbeetle/tigerbeetle](https://github.com/tigerbeetle/tigerbeetle)) by @risu729 in [#8514](#8514) ### New Contributors - @pjeby made their first contribution in [#8520](#8520) - @davireis made their first contribution in [#8517](#8517) - @Aurorxa made their first contribution in [#8511](#8511) ## 📦 Aqua Registry Updates #### New Packages (6) - [`betterleaks/betterleaks`](https://github.com/betterleaks/betterleaks) - [`majorcontext/moat`](https://github.com/majorcontext/moat) - [`princjef/gomarkdoc`](https://github.com/princjef/gomarkdoc) - [`remko/age-plugin-se`](https://github.com/remko/age-plugin-se) - [`sudorandom/fauxrpc`](https://github.com/sudorandom/fauxrpc) - [`swanysimon/mdlint`](https://github.com/swanysimon/mdlint) #### Updated Packages (1) - [`moonrepo/moon`](https://github.com/moonrepo/moon)
get_credential_command_token() and get_git_credential_token() spawn subprocesses (sh -c <cmd> and git credential fill) that inherit the current PATH, including mise shims. When the credential tool (e.g. gh) or git is managed by mise, the shim calls mise exec, which may re-enter token resolution, spawning the same command indefinitely. This causes a recursive fork bomb that can produce thousands of processes and make the system unresponsive (observed load average >1800 on an ARM SBC running k3s). Apply the same shim-stripping pattern used in dependency_env() (PR jdx#8475) and exec_program() (PR jdx#8276): filter dirs::SHIMS out of PATH before spawning the subprocess.
get_credential_command_token() and get_git_credential_token() spawn subprocesses (sh -c <cmd> and git credential fill) that inherit the current PATH, including mise shims. When the credential tool (e.g. gh) or git is managed by mise, the shim calls mise exec, which may re-enter token resolution, spawning the same command indefinitely. This causes a recursive fork bomb that can produce thousands of processes and make the system unresponsive (observed load average >1800 on an ARM SBC running k3s). Apply the same shim-stripping pattern used in dependency_env() (PR jdx#8475) and exec_program() (PR jdx#8276): filter dirs::SHIMS out of PATH before spawning the subprocess.
get_credential_command_token() and get_git_credential_token() spawn subprocesses (sh -c <cmd> and git credential fill) that inherit the current PATH, including mise shims. When the credential tool (e.g. gh) or git is managed by mise, the shim calls mise exec, which may re-enter token resolution, spawning the same command indefinitely. This causes a recursive fork bomb that can produce thousands of processes and make the system unresponsive (observed load average >1800 on an ARM SBC running k3s). Apply the same shim-stripping pattern used in dependency_env() (PR jdx#8475) and exec_program() (PR jdx#8276): filter dirs::SHIMS out of PATH before spawning the subprocess.
get_credential_command_token() and get_git_credential_token() spawn subprocesses (sh -c <cmd> and git credential fill) that inherit the current PATH, including mise shims. When the credential tool (e.g. gh) or git is managed by mise, the shim calls mise exec, which may re-enter token resolution, spawning the same command indefinitely. This causes a recursive fork bomb that can produce thousands of processes and make the system unresponsive (observed load average >1800 on an ARM SBC running k3s). Apply the same shim-stripping pattern used in dependency_env() (PR jdx#8475) and exec_program() (PR jdx#8276): filter dirs::SHIMS out of PATH before spawning the subprocess.
get_credential_command_token() and get_git_credential_token() spawn subprocesses (sh -c <cmd> and git credential fill) that inherit the current PATH, including mise shims. When the credential tool (e.g. gh) or git is managed by mise, the shim calls mise exec, which may re-enter token resolution, spawning the same command indefinitely. This causes a recursive fork bomb that can produce thousands of processes and make the system unresponsive (observed load average >1800 on an ARM SBC running k3s). Apply the same shim-stripping pattern used in dependency_env() (PR jdx#8475) and exec_program() (PR jdx#8276): filter dirs::SHIMS out of PATH before spawning the subprocess.
get_credential_command_token() and get_git_credential_token() spawn subprocesses (sh -c <cmd> and git credential fill) that inherit the current PATH, including mise shims. When the credential tool (e.g. gh) or git is managed by mise, the shim calls mise exec, which may re-enter token resolution, spawning the same command indefinitely. This causes a recursive fork bomb that can produce thousands of processes and make the system unresponsive (observed load average >1800 on an ARM SBC running k3s). Apply the same shim-stripping pattern used in dependency_env() (PR jdx#8475) and exec_program() (PR jdx#8276): filter dirs::SHIMS out of PATH before spawning the subprocess.
get_credential_command_token() and get_git_credential_token() spawn subprocesses (sh -c <cmd> and git credential fill) that inherit the current PATH, including mise shims. When the credential tool (e.g. gh) or git is managed by mise, the shim calls mise exec, which may re-enter token resolution, spawning the same command indefinitely. This causes a recursive fork bomb that can produce thousands of processes and make the system unresponsive (observed load average >1800 on an ARM SBC running k3s). Apply the same shim-stripping pattern used in dependency_env() (PR jdx#8475) and exec_program() (PR jdx#8276): filter dirs::SHIMS out of PATH before spawning the subprocess.
get_credential_command_token() and get_git_credential_token() spawn subprocesses (sh -c <cmd> and git credential fill) that inherit the current PATH, including mise shims. When the credential tool (e.g. gh) or git is managed by mise, the shim calls mise exec, which may re-enter token resolution, spawning the same command indefinitely. This causes a recursive fork bomb that can produce thousands of processes and make the system unresponsive (observed load average >1800 on an ARM SBC running k3s). Apply the same shim-stripping pattern used in dependency_env() (PR jdx#8475) and exec_program() (PR jdx#8276): filter dirs::SHIMS out of PATH before spawning the subprocess.
…8802) ## Summary Three code paths spawn subprocesses that can invoke mise-managed tools (e.g. `gh`, `git`) without stripping mise shims from PATH. When the tool resolves to a mise shim, it re-enters mise, which may trigger the same subprocess again — causing **infinite recursion (fork bomb)**. Observed: load average >1800 on an ARM SBC, system unresponsive. Also reproduced on macOS. ## Root Cause Three subprocess-spawning paths inherit shims in PATH: ### 1. `credential_command` in `src/github.rs` `get_credential_command_token()` runs `sh -c <cmd>` (e.g. `gh auth token`). If `gh` is a mise shim → recursion. ### 2. `git credential fill` in `src/github.rs` `get_git_credential_token()` runs `git credential fill`. If `git` is a mise shim, or git's credential helper invokes `gh` (via `gh auth setup-git`) → recursion. ### 3. `exec()` template function in `src/tera.rs` (primary trigger) When a `.mise.toml` contains: ```toml [env] GITHUB_TOKEN = "{{exec(command='gh auth token')}}" ``` Every `mise hook-env` in that directory runs `gh auth token` via `tera_exec()` with `PRISTINE_ENV`, which includes shims in PATH → `gh` shim → `mise exec` → evaluates env → runs `gh auth token` again → infinite recursion. This is the most common trigger because `exec()` in `[env]` is the idiomatic way to derive env vars from CLI tools. ## Fix Add a shared `file::path_env_without_shims()` helper (next to the existing `which_no_shims()`) that filters `dirs::SHIMS` out of PATH and returns an `OsString` suitable for `.env("PATH", ...)`. Used in all three call sites: - **`src/github.rs`**: `get_credential_command_token()` and `get_git_credential_token()` - **`src/tera.rs`**: `tera_exec()` Follows the same shim-stripping pattern established in: - PR #8475 (`dependency_env()`) - PR #8276 / #8560 (`exec_program()`) - PR #8189 (Windows variant) - PR #8402 (`uv` venv creation via `which_no_shims()`) Related: Discussion #6374 — same user-facing symptom ("Cannot fork") from `exec()` path. ## Reproduction ### Tera exec path (most common) 1. Install `gh` via mise (`gh = "latest"`) 2. Create a project `.mise.toml` with `[env] GITHUB_TOKEN = "{{exec(command='gh auth token')}}"` 3. `cd` into that directory — `mise hook-env` fires, evaluates the template, runs `gh auth token` through the shim → fork bomb ### Credential command path 1. Install `gh` via mise, set `credential_command = "gh auth token"` in mise settings 2. Trigger any GitHub API call → fork bomb ## Changes - **`src/file.rs`**: New `path_env_without_shims()` public helper — shared by all call sites - **`src/github.rs`**: Use shared helper in `get_credential_command_token()` and `get_git_credential_token()` - **`src/tera.rs`**: Use shared helper in `tera_exec()` - **`e2e/cli/test_github_credential_shim_recursion`**: e2e test for credential_command path - **`e2e/cli/test_tera_exec_shim_recursion`**: e2e test for tera exec() path ## Note on env var workaround Setting `MISE_GITHUB_TOKEN=""` does **not** prevent the hang — `resolve_token()` filters empty strings with `.filter(|t| !t.is_empty())`, so it falls through to subprocess-based fallbacks. A non-empty sentinel value (e.g. `MISE_GITHUB_TOKEN="none"`) works for the github.rs paths but the tera `exec()` path is independent of token resolution entirely. ## Test plan - [x] `cargo check` passes - [ ] CI: `cargo test` passes - [ ] CI: e2e test `test_github_credential_shim_recursion` passes - [ ] CI: e2e test `test_tera_exec_shim_recursion` passes - [x] Manual test: project with `exec(command='gh auth token')` in `.mise.toml`, `cd` does not fork-bomb --------- Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
jdx#8475 fixed the fork-bomb where backend version-resolution subprocesses re-entered mise through a shim, by filtering `dirs::SHIMS` (the user shims dir) out of `dependency_env`'s PATH. Devcontainer / Docker setups that use `mise install --system` also have a *system* shims dir at `$MISE_SYSTEM_DATA_DIR/shims`, also on PATH by convention. That dir wasn't filtered, so the same recursion still fired through it. Concrete repro: a workspace `mise.toml` pinning `"npm:yarn" = "1"` together with an uninstalled node trips the npm backend's `npm view yarn versions time --json` call. The user shim is stripped (per jdx#8475), but the system shim still matches PATH lookup → mise re-enters as `npm` → sees the same uninstalled `npm:yarn` → auto-installs → calls `npm view` again — until the container OOMs. Filter both shim dirs in `dependency_env`, mirroring the dual-dir guard that `shims::which_shim` already applies after jdx#8816. Adds a regression test under `e2e/backend/` that plants a shim in `$MISE_SYSTEM_DATA_DIR/shims` and asserts no fork-bomb when an `npm:` backend tool resolves remote versions.
Summary
When a
go:backend tool is configured alongside agoversion that is not installed, and the remote version cache has been cleared (e.g.mise cache clear), subsequentmisecommands spawn infinite child processes until OOM.Root cause
_list_remote_versionsfor thego:backend callsdependency_envto build the environment forgo list. In--shimsmode,PRISTINE_ENVcontains the mise shims directory inPATH(shims are added to the shellPATHdirectly without setting__MISE_DIFF, so they survive the pristine-env reconstruction). Withgo@<configured-version>not installed, no realgobinary is added todependency_env's PATH — the onlygoavailable is its shim.The shim calls
mise exec -- go list ..., which spawns a new mise process. That process has the same config, callsts.resolve()on all tools including thego:backend, hits_list_remote_versions(cache still empty), callsgo listvia the shim again → infinite fork bomb.Fix
Strip
dirs::SHIMSfrom the PATH independency_envbefore returning it. The dependency tool either resolves to a real installed binary or the command fails cleanly — no shim is ever invoked. This is the correct invariant: dependency envs should never rely on mise's own shim layer.The fix applies to all backends using the default
dependency_env(go,npm,gem,dotnet,spm,elixir).Repro
The original Docker repro from the issue runs cleanly with this fix:
Before the fix: fork bombs until OOM. After: exits cleanly with
[].Test plan
e2e/backend/test_go_shim_recursionreproduces the bug (times out without fix, passes with fix)cargo test— 515 unit tests passe2e/backend/test_backend_missing_deps,e2e/cli/test_cache_clear,e2e/cli/test_ls,e2e/cli/test_backends— all passThis PR was authored with Claude Code.