Skip to content

security: require trust check for .tool-versions Tera templates#8675

Merged
jdx merged 3 commits intomainfrom
security/tool-versions-trust-check
Mar 21, 2026
Merged

security: require trust check for .tool-versions Tera templates#8675
jdx merged 3 commits intomainfrom
security/tool-versions-trust-check

Conversation

@jdx
Copy link
Copy Markdown
Owner

@jdx jdx commented Mar 21, 2026

Summary

  • .tool-versions files were processed through Tera's render_str() with the exec() function registered, allowing arbitrary command execution without any trust verification
  • Unlike .mise.toml files which call trust_check() before parsing, .tool-versions bypassed this entirely
  • A malicious .tool-versions in a cloned repo could silently execute code when a user with mise shell activation cd'd into the directory — no trust prompt, no warning
  • This adds trust_check() gating when template syntax ({{, {%, {#) is detected in .tool-versions files
  • Plain .tool-versions files without templates continue to work without requiring trust

Test plan

  • E2E test test_tool_versions_trust_check validates:
    • Untrusted .tool-versions with exec() template is blocked with trust error
    • exec() command does not run (no side effects)
    • Plain .tool-versions without templates works without trust
    • Trusted .tool-versions with templates works after mise trust

🤖 Generated with Claude Code


Note

Medium Risk
Adds a new trust gate on .tool-versions parsing when template syntax is present, affecting config loading behavior and potentially breaking existing templated files until trusted; however the change is narrowly scoped and covered by an e2e test.

Overview
Prevents arbitrary command execution via Tera templating in untrusted .tool-versions files by only running render_str() after trust_check() when template markers ({{, {%, {#) are detected; non-templated .tool-versions continue to parse without trust.

Adds an e2e regression test that verifies untrusted templated .tool-versions are blocked without side effects, and that trusted templated and plain files still work. Also allows security as a valid PR title type in semantic-pr-lint.

Written by Cursor Bugbot for commit 19b1e16. 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 resolves a security vulnerability in how mise handles .tool-versions files. By requiring a trust check for files containing Tera templates, it prevents potential arbitrary command execution from malicious files. The changes include modifications to the tool_versions.rs file to incorporate the trust check and a new E2E test to validate the fix.

Highlights

  • Security Vulnerability: Addresses a critical security vulnerability where .tool-versions files could execute arbitrary commands without trust verification due to Tera template rendering.
  • Trust Check Implementation: Implements a trust_check() gate for .tool-versions files containing Tera template syntax ({{, {%, {#) to prevent unauthorized code execution.
  • Test Coverage: Includes an E2E test (test_tool_versions_trust_check) to validate the trust check mechanism, ensuring untrusted templates are blocked and trusted templates work after mise trust.
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.

@jdx jdx changed the title security: require trust check for .tool-versions Tera templates fix: require trust check for .tool-versions Tera templates Mar 21, 2026
@jdx jdx changed the title fix: require trust check for .tool-versions Tera templates security: require trust check for .tool-versions Tera templates Mar 21, 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 introduces a critical security fix by adding a trust check for .tool-versions files that contain Tera template syntax. This prevents potential arbitrary command execution from untrusted repositories. The change correctly identifies templated files and gates them behind the existing trust_check mechanism, while leaving plain .tool-versions files unaffected. A new end-to-end test is added to validate the fix, covering scenarios for untrusted, trusted, and non-templated files. My review includes a suggestion to improve the robustness of the new test script.

Comment on lines +1 to +5
#!/usr/bin/env bash

# Test that .tool-versions files with Tera templates require trust verification
# This prevents silent arbitrary code execution from untrusted .tool-versions files

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

For improved test robustness, it's a good practice to use trap to ensure cleanup of temporary files, even if the script exits unexpectedly. This avoids leaving artifacts in /tmp.

With this change, you can also remove the explicit rm calls on lines 19 and 32.

#!/usr/bin/env bash

trap 'rm -f /tmp/mise-trust-test-marker' EXIT

# Test that .tool-versions files with Tera templates require trust verification
# This prevents silent arbitrary code execution from untrusted .tool-versions files

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 2 potential issues.

Fix All in Cursor

Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.

let mut cf = Self::init(&path);
let dir = path.parent();
let s = get_tera(dir).render_str(s, &cf.context)?;
let s = if s.contains("{{") || s.contains("{%") || s.contains("{#") {
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Duplicated security-critical template detection logic across files

Low Severity

The template syntax detection check (s.contains("{{") || s.contains("{%") || s.contains("{#")) duplicates the logic in MiseToml::contains_template_syntax. Since this check is a security gate controlling whether trust verification occurs, having it duplicated in two files risks future inconsistency if one is updated but not the other. A shared utility function would be more maintainable.

Fix in Cursor Fix in Web

@greptile-apps
Copy link
Copy Markdown
Contributor

greptile-apps bot commented Mar 21, 2026

Greptile Summary

This PR patches a security vulnerability where .tool-versions files were unconditionally rendered through Tera's render_str() — including the exec() function — without any trust verification, unlike .mise.toml files which already called trust_check() before parsing. A malicious .tool-versions in a cloned repository could silently execute arbitrary commands when a user with mise shell activation cd'd into the directory.

Key changes:

  • parse_str in tool_versions.rs now detects Tera template syntax ({{, {%, {#) before rendering and gates the render_str() call behind trust_check(). Plain .tool-versions files (no template syntax) are unaffected.
  • A new e2e test test_tool_versions_trust_check validates three cases: untrusted templated file is blocked, plain file works without trust, and trusted templated file works after mise trust.
  • The test correctly uses MISE_PARANOID=1 to force hash-based trust verification, bypassing the ci_info::is_ci() auto-trust shortcut that would otherwise make the "untrusted" sub-test a no-op in CI.
  • security is added as a valid conventional-commit type for the PR title linter.

The fix integrates cleanly with the existing trust infrastructure: in paranoid mode parse() already calls trust_check() for all file types (so there is a harmless double-check for templated files in paranoid mode, which is idempotent due to the IS_TRUSTED cache and static mutex). In normal mode the new per-call check in parse_str is the sole gating point for .tool-versions templates.

Confidence Score: 4/5

  • Safe to merge; fixes a real code-execution vulnerability with a minimal, targeted change and a well-structured test.
  • The core fix is correct: template-syntax detection covers all three Tera block types, trust_check is called before render_str so exec() cannot fire on untrusted files, and plain files are unaffected. The e2e test properly uses MISE_PARANOID=1 to force hash-based trust and validates all three cases (blocked, plain, trusted). No P0/P1 issues remain; the concerns raised in previous review threads have been addressed.
  • No files require special attention.

Important Files Changed

Filename Overview
src/config/config_file/tool_versions.rs Adds template-syntax detection before calling trust_check() and render_str(); logic is correct and properly gates exec() execution behind trust verification.
e2e/config/test_tool_versions_trust_check New e2e test correctly uses MISE_PARANOID=1 to force hash-based trust checks, bypassing CI auto-trust; covers the blocked, plain, and trusted cases. Minor concern: the trusted sub-test's exec() output (from echo) contains a trailing newline that is silently absorbed by the parser.
.github/workflows/semantic-pr-lint.yml Adds security as a valid conventional-commit type for the semantic PR title linter — trivial and correct.

Flowchart

%%{init: {'theme': 'neutral'}}%%
flowchart TD
    A[parse_str called] --> B{Template syntax\ndetected in file?}
    B -- No --> C[Use plain string as-is]
    B -- Yes --> D[trust_check called on path]
    D --> E{Is path trusted?}
    E -- Yes --> F[Tera render_str\nexec runs here]
    E -- No, paranoid mode --> G{File hash\nmatches stored hash?}
    G -- Yes --> F
    G -- No --> H[Return Err UntrustedConfig]
    E -- No, CI auto-trust --> F
    E -- No, interactive --> I{Prompt user:\ntrust this config?}
    I -- Yes --> J[Store trust marker] --> F
    I -- No --> H
    F --> K[Parse rendered output into toolset]
    C --> K
Loading

Last reviewed commit: "fix(test): use MISE_..."

jdx and others added 3 commits March 21, 2026 12:18
.tool-versions files were processed through Tera's render_str() with
the exec() function registered, allowing arbitrary command execution
without any trust verification. Unlike .mise.toml files which call
trust_check() before parsing, .tool-versions bypassed this entirely.

This meant a malicious .tool-versions file in a cloned repository could
silently execute code when a user with mise shell activation entered the
directory — no trust prompt, no warning.

Add trust_check() gating to .tool-versions parsing when template syntax
({{, {%, {#) is detected. Plain .tool-versions files without templates
continue to work without requiring trust.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Address review feedback: is_trusted() auto-trusts in CI via
ci_info::is_ci(), which would bypass the trust gate under test.
MISE_PARANOID=1 forces hash-based trust checking. Also fix the
trusted template sub-test to properly validate template rendering.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@jdx jdx force-pushed the security/tool-versions-trust-check branch from 0fc93fa to 19b1e16 Compare March 21, 2026 11:18
@github-actions
Copy link
Copy Markdown

Hyperfine Performance

mise x -- echo

Command Mean [ms] Min [ms] Max [ms] Relative
mise-2026.3.9 x -- echo 25.9 ± 0.4 25.1 30.5 1.00
mise x -- echo 26.0 ± 0.4 24.8 28.4 1.00 ± 0.02

mise env

Command Mean [ms] Min [ms] Max [ms] Relative
mise-2026.3.9 env 25.4 ± 0.8 24.3 32.0 1.00
mise env 25.7 ± 0.8 24.4 38.0 1.01 ± 0.05

mise hook-env

Command Mean [ms] Min [ms] Max [ms] Relative
mise-2026.3.9 hook-env 26.0 ± 0.4 25.1 27.7 1.00
mise hook-env 26.2 ± 0.4 25.2 28.0 1.01 ± 0.02

mise ls

Command Mean [ms] Min [ms] Max [ms] Relative
mise-2026.3.9 ls 25.2 ± 0.7 24.0 32.6 1.00
mise ls 25.3 ± 0.5 24.3 29.8 1.01 ± 0.04

xtasks/test/perf

Command mise-2026.3.9 mise Variance
install (cached) 157ms 156ms +0%
ls (cached) 87ms 86ms +1%
bin-paths (cached) 89ms 89ms +0%
task-ls (cached) 867ms 842ms +2%

@jdx jdx merged commit 195ef6d into main Mar 21, 2026
35 of 36 checks passed
@jdx jdx deleted the security/tool-versions-trust-check branch March 21, 2026 12:07
dportalesr added a commit to dportalesr/mise that referenced this pull request Apr 3, 2026
PR jdx#8675 added trust checks for .tool-versions files containing Tera
templates, but the error path in task_list.rs still checks trust on
all config files regardless of type or content. This causes plain
.tool-versions files (no templates) to trigger trust errors.

Skip trust verification for .tool-versions files that don't contain
template syntax ({{, {%, {#}), consistent with the parsing logic.
dportalesr added a commit to dportalesr/mise that referenced this pull request Apr 4, 2026
PR jdx#8675 added trust checks for .tool-versions files containing Tera
templates, but the error path in task_list.rs still checks trust on
all config files regardless of type or content. This causes plain
.tool-versions files (no templates) to trigger trust errors.

Skip trust verification for .tool-versions files that don't contain
template syntax ({{, {%, {#}), consistent with the parsing logic.
jdx pushed a commit that referenced this pull request Apr 4, 2026
## Summary

PR #8675 added trust gating for `.tool-versions` files containing Tera
template syntax, but `task_list.rs` still calls `is_trusted()` on
**all** config files when checking for untrusted configs. This causes
plain `.tool-versions` files (just tool names and versions, no
templates) to trigger trust errors like:

```
mise ERROR Config file(s) in ~/dev/myproject are not trusted: ~/dev/myproject/.tool-versions
Trust them with `mise trust`.
```

This skips the trust check for `.tool-versions` files that don't contain
template syntax (`{{`, `{%`, `{#`), consistent with the parsing logic
added in #8675.

## Changes

- `src/task/task_list.rs`: Added `is_plain_tool_versions()` helper that
checks if a path is a `.tool-versions` file without Tera template
markers. The `err_no_task` trust check now skips these files since they
can't define tasks or execute code.

## Test plan

- [ ] Plain `.tool-versions` (e.g. `ruby 3.3.3\nnodejs 14.21.3`) no
longer triggers trust error
- [ ] `.tool-versions` with template syntax (e.g. `nodejs {{ exec("echo
20") }}`) still requires trust
- [ ] `.mise.toml` trust behavior unchanged

---------

Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
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