Skip to content

fix: strip shims from PATH in credential and template subprocesses#8802

Merged
jdx merged 2 commits intojdx:mainfrom
antonioacg:fix/github-token-shim-recursion
Mar 30, 2026
Merged

fix: strip shims from PATH in credential and template subprocesses#8802
jdx merged 2 commits intojdx:mainfrom
antonioacg:fix/github-token-shim-recursion

Conversation

@antonioacg
Copy link
Copy Markdown
Contributor

@antonioacg antonioacg commented Mar 28, 2026

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:

[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:

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

  • 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
  • Manual test: project with exec(command='gh auth token') in .mise.toml, cd does not fork-bomb

@greptile-apps
Copy link
Copy Markdown
Contributor

greptile-apps bot commented Mar 28, 2026

Greptile Summary

This PR fixes a fork-bomb vulnerability where subprocess-spawning code paths in mise inherited the shims directory in PATH, causing mise-managed tools (e.g. gh, git) to re-enter mise through their shims and trigger infinite recursion. The fix adds two shared helpers in src/file.rs and applies them to three call sites: get_credential_command_token, get_git_credential_token (both in src/github.rs), and tera_exec in src/tera.rs.

  • Core fix is correct and well-designed: path_env_without_shims() (for PATH_NON_PRISTINE) and strip_shims_from_path() (for arbitrary PATH strings like PRISTINE_ENV) follow the same shim-stripping pattern established in earlier PRs (fix(backend): strip mise shims from dependency_env PATH to prevent fork bomb #8475, fix(exec): strip shims from PATH on Unix to prevent infinite recursion #8276, fix(exec): resolve wrapper recursion when shims are in PATH #8560). Both functions have correct join_paths error fallbacks.
  • src/github.rs: Both subprocess-spawning functions are correctly patched with a single .env(\"PATH\", &path_without_shims) call.
  • src/tera.rs: The clone-then-mutate approach for env_no_shims is appropriate since full_env passes the entire map to the subprocess.
  • test_tera_exec_shim_recursion is an effective regression test: assert_contains \"mise env -s bash\" \"ghp_test_token_from_real_gh\" verifies the real binary was resolved (if the shim were used, it exits 1 and the token wouldn't appear).
  • test_github_credential_shim_recursion has a broken assertion (see inline comment): run_with_timeout merges the subprocess's stdout+stderr into its own stdout, so 2>/tmp/credential_test_stderr captures only the shell wrapper's stderr (effectively nothing). The assert_not_contains check on that file passes trivially in all cases and will not catch a regression of the credential path fix.

Confidence Score: 4/5

The runtime fix is correct and safe to merge; the credential e2e test needs a revision to actually guard against regression.

The three production-code changes are sound and follow established patterns in the codebase. However, test_github_credential_shim_recursion contains a broken assertion that will never fire — if the credential-path fix is later reverted, this test won't catch it. That is a real gap in test coverage for an important stability fix, warranting a 4 rather than 5.

e2e/cli/test_github_credential_shim_recursion — the assert_not_contains check at line 62 is ineffective and should be replaced with a mechanism that actually detects shim invocation (e.g. a sentinel file written by the fake shim).

Important Files Changed

Filename Overview
src/file.rs Adds two well-documented helpers: path_env_without_shims() (filters PATH_NON_PRISTINE) and strip_shims_from_path() (strips shims from an arbitrary PATH string). Both have correct join_paths error fallbacks. No issues found.
src/github.rs Correctly applies path_env_without_shims() to both get_credential_command_token and get_git_credential_token, preventing shim re-entry. Implementation is clean and consistent.
src/tera.rs Correctly strips shims from the PRISTINE_ENV-derived env map before spawning the tera exec() subprocess. The clone-then-modify pattern is appropriate since the full env map is passed via full_env.
e2e/cli/test_github_credential_shim_recursion Regression test for the credential path has a broken assertion: run_with_timeout merges subprocess stdout+stderr into its own stdout, so /tmp/credential_test_stderr is always empty and assert_not_contains passes trivially regardless of whether the shim is invoked.
e2e/cli/test_tera_exec_shim_recursion Effective regression test: assert_contains "mise env -s bash" "ghp_test_token_from_real_gh" implicitly verifies the real binary was found (shim exits 1 and would cause the token to be absent/errored).

Sequence Diagram

sequenceDiagram
    participant User
    participant mise
    participant tera_exec
    participant github_rs as github.rs
    participant subprocess

    User->>mise: cd into project / hook-env
    mise->>tera_exec: evaluate exec() template
    tera_exec->>tera_exec: clone PRISTINE_ENV, strip shims via strip_shims_from_path()
    tera_exec->>subprocess: spawn shell (PATH without shims)
    subprocess-->>tera_exec: real binary output

    User->>mise: GitHub API call (ls-remote, install)
    mise->>github_rs: resolve_token()
    github_rs->>github_rs: path_env_without_shims()
    github_rs->>subprocess: sh -c credential_command (PATH without shims)
    subprocess-->>github_rs: token
    github_rs->>subprocess: git credential fill (PATH without shims)
    subprocess-->>github_rs: password= token
Loading

Reviews (8): Last reviewed commit: "[autofix.ci] apply automated fixes" | Re-trigger Greptile

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 prevents infinite recursion during credential resolution by stripping mise shims from the PATH environment variable in both get_credential_command_token and get_git_credential_token. A review comment suggests extracting the duplicated path-filtering logic into a shared helper function to improve maintainability and reduce code duplication.

@antonioacg antonioacg force-pushed the fix/github-token-shim-recursion branch 2 times, most recently from bb1d42b to 186f002 Compare March 28, 2026 00:27
@antonioacg
Copy link
Copy Markdown
Contributor Author

@jdx — This is the same class of shim-recursion fork bomb fixed in PRs #8475, #8276, #8560, #8402, and #8189, but in the two remaining subprocess-spawning paths in src/github.rs (credential_command and git credential fill). Follows the same pattern, includes an e2e test. Would appreciate a review when you get a chance.

@antonioacg antonioacg force-pushed the fix/github-token-shim-recursion branch from 186f002 to aa48e25 Compare March 28, 2026 00:44
@antonioacg antonioacg changed the title fix(github): strip shims from PATH in credential subprocesses fix: strip shims from PATH in credential and template subprocesses Mar 28, 2026
@antonioacg
Copy link
Copy Markdown
Contributor Author

Updated — found a third recursion vector that was the primary trigger all along:

src/tera.rsexec() template function. When .mise.toml has GITHUB_TOKEN = "{{exec(command='gh auth token')}}", every mise hook-env runs gh auth token via tera_exec() with PRISTINE_ENV (which includes shims in PATH) → fork bomb.

This is independent of the src/github.rs token resolution — it's the Tera template engine running a subprocess without stripping shims. The src/github.rs fixes are still needed (credential_command and git credential fill), but the tera fix covers the most common real-world trigger.

@jdx would appreciate a review when you get a chance — this affects anyone who uses exec() in [env] with a mise-managed tool.

@antonioacg
Copy link
Copy Markdown
Contributor Author

Linking related discussion: #6374 — same user-facing symptom ("Cannot fork") caused by the tera exec() path that this PR fixes. The fix applied at the time (PR #8475) addressed dependency_env() but not tera_exec() itself.

Also note: get_git_credential_token() was introduced in PR #8742 (merged 2026-03-24) — 3 days ago — without shim stripping.

@antonioacg antonioacg force-pushed the fix/github-token-shim-recursion branch 3 times, most recently from 927154e to 5fc984e Compare March 28, 2026 02:55
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.
@antonioacg antonioacg force-pushed the fix/github-token-shim-recursion branch from 5fc984e to 0f0f4b6 Compare March 28, 2026 05:11
@jdx jdx merged commit 0b2eae9 into jdx:main Mar 30, 2026
32 of 34 checks passed
mise-en-dev added a commit that referenced this pull request Mar 31, 2026
### 🚀 Features

- **(python)** add GitHub provenance verification for prebuilt binaries
by @malept in [#8820](#8820)

### 🐛 Bug Fixes

- **(ci)** use rustls-native-roots for Windows CI build by @jdx in
[#8822](#8822)
- **(go)** improve version fetching logic to support deeply nested
sub-modules by @roele in [#8823](#8823)
- **(shim)** prevent infinite recursion when system shims dir is on PATH
by @andrewthauer in [#8816](#8816)
- go backend missing supports_lockfile_url() override by
@palootcenas-outreach in [#8790](#8790)
- strip shims from PATH in credential and template subprocesses by
@antonioacg in [#8802](#8802)

### 📚 Documentation

- fix typo in shims documentation for fish by @roele in
[#8798](#8798)

### 📦️ Dependency Updates

- update ghcr.io/jdx/mise:alpine docker digest to 3e6d001 by
@renovate[bot] in [#8794](#8794)
- pin dependencies by @renovate[bot] in
[#8793](#8793)

### 📦 Registry

- fix flutter version sorting by @roele in
[#8818](#8818)
- add svgo (npm:svgo) by @3w36zj6 in
[#8817](#8817)

### New Contributors

- @antonioacg made their first contribution in
[#8802](#8802)
- @palootcenas-outreach made their first contribution in
[#8790](#8790)

## 📦 Aqua Registry Updates

#### New Packages (3)

- [`RasKrebs/sonar`](https://github.com/RasKrebs/sonar)
- [`emacs-eask/cli`](https://github.com/emacs-eask/cli)
-
[`superradcompany/microsandbox`](https://github.com/superradcompany/microsandbox)

#### Updated Packages (4)

- [`dimo414/bkt`](https://github.com/dimo414/bkt)
- [`lxc/incus`](https://github.com/lxc/incus)
-
[`shinagawa-web/gomarklint`](https://github.com/shinagawa-web/gomarklint)
- [`updatecli/updatecli`](https://github.com/updatecli/updatecli)
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