Skip to content

feat(config): load safe mise.toml files without trust#10360

Merged
jdx merged 6 commits into
mainfrom
feat/trust-safe-configs
Jun 12, 2026
Merged

feat(config): load safe mise.toml files without trust#10360
jdx merged 6 commits into
mainfrom
feat/trust-safe-configs

Conversation

@jdx

@jdx jdx commented Jun 12, 2026

Copy link
Copy Markdown
Owner

Summary

mise.toml files that cannot execute code at load time or change mise's behavior without an explicit user action no longer require trust. A config is exempt from the trust gate only when:

  • it contains no Tera template syntax — checked both on the raw body ({{, {%, {#) and on every decoded TOML string (keys/values, any depth) so escaped delimiters can't slip through — and
  • its top-level keys are limited to min_version, [tasks], and [tools] entries whose values are plain version strings (or arrays of them)

Tools install and tasks run only on explicit commands (mise install, mise run), so loading these configs executes nothing. Template-free file tasks (mise-tasks/ scripts and included TOML task files) are likewise loadable without trust — templates are the only thing that can execute while tasks load.

Everything else keeps the existing trust prompt/error: [env], [hooks], [settings], [alias], [plugins], [task_config], env_file/dotenv, tool entries with options (tables) — since options like postinstall run shell commands and install_env can alter the install environment — and any file with template syntax. Paranoid mode still requires trust for every file, and ignored configs stay unloaded even when safe.

The check is allowlist-based, so an unrecognized or future config construct fails safe (still prompts for trust).

Security hardening

  • Escaped-template trust bypass (critical, caught in review). is_safe_config_body originally only scanned the raw TOML body, so tiny = "{{ exec(...) }}" — which TOML decodes to {{ exec(...) }} — passed as "safe" and then executed via Tera when the tool version / task field rendered, with no trust prompt. Fixed by recursively re-checking every decoded TOML string for template syntax. Regression tests added (unit + e2e) for both the tools and tasks paths.
  • Task-include trust hole (pre-existing). load_tasks_in_dir skipped the include trust check whenever any config existed at the root, even one loaded without trust (e.g. a plain .tool-versions), so a templated mise-tasks/*.toml could render exec() at mise tasks time. Includes now require trust unless a root config was actually trusted (new config_file::is_path_trusted).

Other fixes

  • Tracked safe configs. get_tracked_config_files skipped any path without a trust marker, which safe configs don't create — so their tool pins went missing from mise ls --all-sources, mise upgrade, and mise prune. Safe configs are now loaded there too (MiseToml::path_is_trust_exempt). Regression test: e2e/config/test_trust_safe_config_tracked.

Tests

  • Unit test_is_safe_config_body: tools/tasks safe cases, plus rejection of templates, tool-options, and escaped templates in tool versions, task fields, and keys
  • e2e/config/test_trust_safe_config: safe tools+tasks (config and file tasks) load/run with no trust marker; literal- and escaped-template variants in versions, task fields, and file-task headers, plus tool-options and [env], are all blocked with nothing executing
  • e2e/config/test_trust_safe_config_tracked: safe config's pins survive tracked-config reload from another directory
  • test_task_untrusted_config_error / test_task_monorepo_trust updated for the new semantics; all other trust e2e tests pass unchanged
  • Trust-marker e2e assertions made precise (check for marker files, not just the directory)

🤖 Generated with Claude Code

Summary by CodeRabbit

  • New Features

    • Safe config files (only min_version, plain tool versions, and tasks without templates/options) auto-load without a trust prompt.
  • Documentation

    • Help text, man page, CLI docs, and FAQ updated to define which configs auto-load and which require trust.
  • Refactor

    • Trust and load logic enhanced to recognize safe configs and detect encoded/escaped template markers.
  • Tests

    • Added unit and E2E tests for safe-config loading, tracked safe configs, escaped-template edge cases, and unsafe cases requiring trust.

Note

High Risk
Changes when untrusted config is parsed and when templates can run at load time; mistakes could allow code execution without a trust prompt.

Overview
Untrusted mise.toml files that only pin tools and define tasks can now load without mise trust, when their content is allowlisted (min_version, plain [tools] strings/arrays, [tasks] with no tool-option tables) and has no Tera templates. Parsing skips trust_check via is_trust_exempt / path_is_trust_exempt; everything else ([env], hooks, settings, templates, postinstall, etc.) still requires trust, and paranoid / ignored configs stay gated.

Security tightening: decoded TOML strings are scanned for templates so Unicode-escaped {{ exec(...) }} cannot bypass a raw-body check. Task includes and mise-tasks files only auto-load without trust when template-free; templated #MISE headers and included TOML still require trust. Trusted vs trust-exempt is split with is_path_trusted so safe configs cannot vouch for includes, and tracked-config reload no longer drops safe configs missing a trust marker.

Docs/CLI help and e2e tests document and cover safe load, escaped-template blocks, and tracked-config visibility.

Reviewed by Cursor Bugbot for commit 1dd9a7e. Bugbot is set up for automated code reviews on this repo. Configure here.

Config files that only contain min_version and [tools] entries with
plain version strings cannot execute code or change mise's behavior,
so they no longer require trusting the file before parsing. Anything
else — env vars, tasks, hooks, settings, aliases, templates, tool
options like postinstall/install_env — still requires trust, as does
everything in paranoid mode. Configs the user chose to ignore stay
unloaded even when safe.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@coderabbitai

coderabbitai Bot commented Jun 12, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 39b3a584-86b6-4088-85c3-71c52b03ed79

📥 Commits

Reviewing files that changed from the base of the PR and between c01306d and 1dd9a7e.

📒 Files selected for processing (1)
  • e2e/tasks/test_task_include_trust_escaped
🚧 Files skipped from review as they are similar to previous changes (1)
  • e2e/tasks/test_task_include_trust_escaped

📝 Walkthrough

Walkthrough

Defines a “safe config” TOML subset exempt from interactive trust checks, wires path-level trust decisions into config/include/task loading, adds decoded-template/header parsing checks, updates docs/help text, and adds unit and E2E tests for safe and unsafe cases.

Changes

Safe config auto-loading without trust

Layer / File(s) Summary
Trust exemption logic and unit tests
src/config/config_file/mise_toml.rs
is_safe_config_body() validates configs restricted to min_version, inert [tasks], and plain [tools] version strings; is_trust_exempt() combines paranoid/ignored checks with the safe-body predicate; MiseToml::from_str conditionally calls trust_check() only when not exempt. Unit tests cover template syntax, exec(...), tool option tables, templated task fields, escaped-delimiter edge cases, other top-level sections, and invalid TOML.
Trust helper and trust_check refactor
src/config/config_file/mod.rs
Adds is_path_trusted(path: &Path) -> bool and refactors trust_check to use the new helper for early exits when a path or its trust-root is already trusted.
Task include trust gating
src/config/mod.rs
Task-include loading now requires trust only if originating configs are untrusted and the include file may execute templates; introduces task_include_requires_trust and uses is_path_trusted to decide when to run trust_check for includes.
Task header parsing & decoded-template checks
src/task/mod.rs
Adds parse_mise_header_toml to extract header TOML, reuses it for decoded-template detection in script headers and .toml includes, and updates Task::from_path and tests accordingly.
End-to-end and integration tests
e2e/config/test_trust_safe_config, e2e/config/test_trust_safe_config_tracked, e2e/tasks/*
Bash E2E tests confirm safe configs load and run without prompting or creating trusted markers, and verify that templated tool strings, templated task fields, tool option forms, escaped-delimiter cases, and [env] force trust; tracked-config and include-escape tests added/updated.
User documentation of safe config criteria
docs/cli/trust.md, docs/faq.md, mise.usage.kdl, src/cli/trust.rs, man/man1/mise.1
CLI help, FAQ, usage docs, man page, and module docs define “safe config files” (only min_version, plain [tools], and inert [tasks]) and clarify which config features still require trust.

Sequence Diagram (high-level config load & trust decision)

sequenceDiagram
  participant CLI as mise CLI
  participant FromStr as MiseToml::from_str
  participant IsExempt as MiseToml::is_trust_exempt
  participant SafeBody as is_safe_config_body
  participant Trust as trust_check
  participant ConfigLoader as Config loader
  participant IncludeChecker as task_include_requires_trust
  participant TaskRunner as Task execution

  CLI->>FromStr: request parse/load (path, body)
  FromStr->>IsExempt: evaluate(body, path)
  IsExempt->>SafeBody: scan for templates & allowed keys
  IsExempt-->>FromStr: exempt? (bool)
  FromStr->>Trust: run when not exempt
  FromStr->>ConfigLoader: hand off parsed config
  ConfigLoader->>IncludeChecker: decide include trust per-path
  IncludeChecker->>Trust: run if include requires trust
  ConfigLoader->>TaskRunner: execute/install tasks/tools (explicit commands)
Loading

🎯 4 (Complex) | ⏱️ ~45 minutes

  • jdx/mise#10355: Also modifies src/config/mod.rs around when trust_check runs for task include files; directly related to include/trust gating.
  • jdx/mise#10225: Intersects with task-include handling and how include paths are resolved/rendered prior to trust decisions.
  • jdx/mise#10191: Related prior changes touching task include loading order and precedence, which interacts with trust gating.

"🐰 I nibble on TOML, neat and small,
A min_version hop, tools standing tall,
No prompts in the burrow, just quiet and clean,
Safe configs load softly, unseen —
I twirl my whiskers, then off to the green."

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately summarizes the main change: enabling load of safe mise.toml files without requiring user trust. This is the central feature introduced across all modified files.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.
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.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.


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

Comment thread src/config/config_file/mise_toml.rs
@greptile-apps

greptile-apps Bot commented Jun 12, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

This PR allows "safe" mise.toml files — those containing only min_version, plain-string [tools] entries, and template-free [tasks] — to load without a trust prompt or marker, since none of those sections execute code at load time. The allowlist check is hardened with both a raw-text scan and a decoded-TOML scan so Unicode-escaped Tera delimiters (\u007b\u007b) can't slip through.

  • is_safe_config_body implements the allowlist gate with two layers of template detection; is_trust_exempt wraps it with paranoid-mode and ignore-list short-circuits.
  • is_path_trusted (actual trust marker) is now distinguished from path_is_trust_exempt (exempt from needing one), fixing the task-include vouch logic in load_file_tasks and load_tasks_in_dir.
  • get_tracked_config_files now includes safe-exempt configs via path_is_trust_exempt, fixing missing tool pins in mise ls --all-sources/mise upgrade/mise prune.

Confidence Score: 5/5

Safe to merge; the allowlist is conservative, the template bypass is closed at both the raw-text and decoded-TOML layers, and paranoid mode / ignored configs remain fully gated.

The two-layer template check (raw scan + recursive decoded-TOML scan) correctly closes the Unicode-escape bypass. The is_path_trusted / path_is_trust_exempt distinction is consistently applied across all three load paths. Tests cover safe load, escaped-template rejection, tracked-config reload, and updated trust-error scenarios. No code executes at load time for allowlisted content, and every unrecognised key fails safe by requiring trust.

No files require special attention.

Important Files Changed

Filename Overview
src/config/config_file/mise_toml.rs Adds is_trust_exempt/is_safe_config_body/path_is_trust_exempt; safety check runs before trust_check in from_str, raw + decoded template scans both implemented correctly
src/config/mod.rs Trust logic updated in get_tracked_config_files, load_file_tasks, and load_tasks_in_dir; is_path_trusted (actual marker) correctly distinguished from path_is_trust_exempt (exempt from needing one)
src/config/config_file/mod.rs New is_path_trusted helper; trust_check now delegates to it, replacing the previous inline `is_trusted(&config_root)
src/task/mod.rs Extracts parse_mise_header_toml helper and adds file_has_decoded_template to catch unicode-escaped Tera delimiters in .toml task files and #MISE script headers
e2e/config/test_trust_safe_config Comprehensive e2e coverage: safe load, task run without trust, escaped template in tool version and task description blocked, tool options blocked, env vars blocked
e2e/tasks/test_task_include_trust_escaped New e2e test for escaped-delimiter bypass via .toml task include and #MISE script header; plain file task sanity-check included

Reviews (5): Last reviewed commit: "test(task): exercise decoded #MISE heade..." | Re-trigger Greptile

Comment thread e2e/config/test_trust_safe_config
if contains_template_syntax(body) {
return false;
}
let Ok(toml::Value::Table(table)) = toml::from_str::<toml::Value>(body) else {

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.

P2 TOML parsed twice per safe config load

is_safe_config_body calls toml::from_str::<toml::Value>(body) to validate the structure, and then from_str on MiseToml calls toml::Deserializer::parse(body) again immediately after. For the common case — the plain tools-only config this feature is optimised for — this doubles the parsing work on every load. Caching the parsed toml::Value or combining the safety check with the existing parse pass would avoid the redundant work, though the impact is small for typical config sizes.

Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!

Fix in Claude Code

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

Actionable comments posted: 2

🧹 Nitpick comments (1)
e2e/config/test_trust_safe_config (1)

30-33: 📐 Maintainability & Code Quality | ⚡ Quick win

Make trust-required assertions specific to the canonical error text.

grep -qi "trust" is broad and can pass on unrelated output. Matching the canonical untrusted-config wording makes this E2E check much more reliable.

Suggested assertion tightening
-echo "$output" | grep -qi "trust" || fail "expected trust error for template, got: $output"
+echo "$output" | grep -q "are not trusted" || fail "expected trust error for template, got: $output"
+echo "$output" | grep -q 'Trust them with `mise trust`' || fail "expected trust guidance for template, got: $output"

-echo "$output" | grep -qi "trust" || fail "expected trust error for tool options, got: $output"
+echo "$output" | grep -q "are not trusted" || fail "expected trust error for tool options, got: $output"
+echo "$output" | grep -q 'Trust them with `mise trust`' || fail "expected trust guidance for tool options, got: $output"

-echo "$output" | grep -qi "trust" || fail "expected trust error for env, got: $output"
+echo "$output" | grep -q "are not trusted" || fail "expected trust error for env, got: $output"
+echo "$output" | grep -q 'Trust them with `mise trust`' || fail "expected trust guidance for env, got: $output"

Also applies to: 39-40, 47-48

🤖 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/config/test_trust_safe_config` around lines 30 - 33, The test uses a
broad check echo "$output" | grep -qi "trust" which can match unrelated text;
update the assertions that check the command output (the output variable
produced by `output=$(MISE_YES=0 mise ls 2>&1 || true)` and the subsequent grep
lines at the other occurrences) to match the canonical untrusted-config error
string exactly (use the precise wording the CLI emits for "template requires
trust" / "untrusted config" instead of just "trust") so the E2E assertions on
lines around the `output` variable are specific and deterministic.
🤖 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.

Inline comments:
In `@mise.usage.kdl`:
- Around line 3507-3509: Update the "Safe config files" paragraph to state that
`[tools]` entries may be either plain version strings or arrays of plain version
strings (i.e., lists of simple version strings with no templates or tool
options) in addition to `min_version`; locate the paragraph referencing
"min_version and [tools] entries with plain version strings" and expand it to
explicitly allow arrays of plain strings so the wording matches the PR behavior
contract.

In `@src/config/config_file/mise_toml.rs`:
- Around line 1979-2001: The current is_safe_config_body only checks for Tera
template markers in the raw TOML body, allowing Unicode-escaped templates inside
tool version strings to bypass the check; update is_safe_config_body (the
function) so after parsing the toml::Value::Table it additionally iterates the
"tools" table values and for each string or array element decodes/inspects the
actual string values and runs contains_template_syntax on those decoded strings
(reject if any contains template syntax), and add a unit test in
test_is_safe_config_body that includes a tool entry like node = "\u007b\u007b
exec(command='echo 20') \u007d\u007d" to assert the function returns false.

---

Nitpick comments:
In `@e2e/config/test_trust_safe_config`:
- Around line 30-33: The test uses a broad check echo "$output" | grep -qi
"trust" which can match unrelated text; update the assertions that check the
command output (the output variable produced by `output=$(MISE_YES=0 mise ls
2>&1 || true)` and the subsequent grep lines at the other occurrences) to match
the canonical untrusted-config error string exactly (use the precise wording the
CLI emits for "template requires trust" / "untrusted config" instead of just
"trust") so the E2E assertions on lines around the `output` variable are
specific and deterministic.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 3f3640d4-9db9-4a3e-b24b-ce369c65187b

📥 Commits

Reviewing files that changed from the base of the PR and between 69b3748 and 1be6abf.

📒 Files selected for processing (6)
  • docs/cli/trust.md
  • docs/faq.md
  • e2e/config/test_trust_safe_config
  • mise.usage.kdl
  • src/cli/trust.rs
  • src/config/config_file/mise_toml.rs

Comment thread mise.usage.kdl Outdated
Comment thread src/config/config_file/mise_toml.rs
@github-actions

github-actions Bot commented Jun 12, 2026

Copy link
Copy Markdown

Hyperfine Performance

mise x -- echo

Command Mean [ms] Min [ms] Max [ms] Relative
mise-2026.6.5 x -- echo 24.9 ± 1.7 20.2 31.2 1.00
mise x -- echo 26.3 ± 2.3 21.5 47.7 1.06 ± 0.12

mise env

Command Mean [ms] Min [ms] Max [ms] Relative
mise-2026.6.5 env 24.5 ± 1.7 20.6 30.2 1.05 ± 0.11
mise env 23.4 ± 1.9 19.0 29.9 1.00

mise hook-env

Command Mean [ms] Min [ms] Max [ms] Relative
mise-2026.6.5 hook-env 22.6 ± 1.6 19.2 29.8 1.00
mise hook-env 24.0 ± 1.8 19.8 29.5 1.06 ± 0.11

mise ls

Command Mean [ms] Min [ms] Max [ms] Relative
mise-2026.6.5 ls 19.7 ± 1.4 16.4 24.4 1.00
mise ls 20.5 ± 1.7 16.2 25.6 1.04 ± 0.11

xtasks/test/perf

Command mise-2026.6.5 mise Variance
install (cached) 156ms 158ms -1%
ls (cached) 70ms 71ms -1%
bin-paths (cached) 83ms 78ms +6%
task-ls (cached) 153ms 154ms +0%

Defining a task is inert — it only executes when the user explicitly
runs it — so [tasks] no longer disqualifies a config from loading
without trust. Template-free task include files (mise-tasks/ scripts
and TOML task files) are likewise loadable untrusted, since templates
are the only thing that can execute while tasks load.

This also fixes a latent hole: task includes skipped their trust check
whenever any config existed at the root, even one that loaded without
being trusted (e.g. a plain .tool-versions), letting a templated task
file render exec() at load time. Includes now require trust unless a
config at the root was actually trusted.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Comment thread src/config/mod.rs

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

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
e2e/tasks/test_task_monorepo_trust (1)

46-64: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Unset CI auto-trust before the new negative-path assertion.

This script now asserts an untrusted-config error, but is_trusted() short-circuits to true in CI. Without the same unset CI GITHUB_ACTIONS GITHUB_ACTION setup used in the other trust e2es, this case won't actually exercise the failure path on GitHub Actions.

🤖 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/tasks/test_task_monorepo_trust` around lines 46 - 64, The test fails to
exercise the untrusted path in CI because is_trusted() is short-circuited;
before the negative-path assertion that checks for "are not trusted" (the
assert_contains line) add the same CI auto-trust disabling used in other trust
e2es: unset or clear CI, GITHUB_ACTIONS, and GITHUB_ACTION environment variables
(in addition to setting MISE_TRUSTED_CONFIG_PATHS="") so is_trusted() will
evaluate false and the trust error path is tested.
♻️ Duplicate comments (1)
src/config/config_file/mise_toml.rs (1)

1984-2005: 🔒 Security & Privacy | 🔴 Critical | ⚡ Quick win

Base these trust checks on decoded TOML strings, not raw file text. is_safe_config_body in src/config/config_file/mise_toml.rs and task_include_requires_trust in src/config/mod.rs both only scan the raw source, so unicode-escaped delimiters like \u007b\u007b exec(...) \u007d\u007d bypass the trust gate. After TOML parsing those become real Tera templates, and the later task/tool render paths can execute them. Parse the TOML first, re-check decoded string values, and add regressions for escaped-template root configs and task include files.

🤖 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/config/config_file/mise_toml.rs` around lines 1984 - 2005, The current
trust checks in is_safe_config_body (and similarly task_include_requires_trust)
only scan the raw file text so unicode-escaped template delimiters can bypass
detection; change the logic to parse the TOML first (use toml::from_str to get
toml::Value::Table), then re-run contains_template_syntax on the decoded string
values (for keys like "min_version", "tasks", and inside the "tools" table: each
version string and each string element of version arrays) and treat any decoded
string containing template syntax as unsafe; update task_include_requires_trust
to perform the same parse-then-inspect flow for included task files and add
regression tests covering unicode-escaped templates in root configs and task
include files.
🤖 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.

Outside diff comments:
In `@e2e/tasks/test_task_monorepo_trust`:
- Around line 46-64: The test fails to exercise the untrusted path in CI because
is_trusted() is short-circuited; before the negative-path assertion that checks
for "are not trusted" (the assert_contains line) add the same CI auto-trust
disabling used in other trust e2es: unset or clear CI, GITHUB_ACTIONS, and
GITHUB_ACTION environment variables (in addition to setting
MISE_TRUSTED_CONFIG_PATHS="") so is_trusted() will evaluate false and the trust
error path is tested.

---

Duplicate comments:
In `@src/config/config_file/mise_toml.rs`:
- Around line 1984-2005: The current trust checks in is_safe_config_body (and
similarly task_include_requires_trust) only scan the raw file text so
unicode-escaped template delimiters can bypass detection; change the logic to
parse the TOML first (use toml::from_str to get toml::Value::Table), then re-run
contains_template_syntax on the decoded string values (for keys like
"min_version", "tasks", and inside the "tools" table: each version string and
each string element of version arrays) and treat any decoded string containing
template syntax as unsafe; update task_include_requires_trust to perform the
same parse-then-inspect flow for included task files and add regression tests
covering unicode-escaped templates in root configs and task include files.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 6f6e1167-e97b-44b8-8184-45e2e4dc2227

📥 Commits

Reviewing files that changed from the base of the PR and between 1be6abf and 46e02fd.

📒 Files selected for processing (10)
  • docs/cli/trust.md
  • docs/faq.md
  • e2e/config/test_trust_safe_config
  • e2e/tasks/test_task_monorepo_trust
  • e2e/tasks/test_task_untrusted_config_error
  • mise.usage.kdl
  • src/cli/trust.rs
  • src/config/config_file/mise_toml.rs
  • src/config/config_file/mod.rs
  • src/config/mod.rs
✅ Files skipped from review due to trivial changes (2)
  • mise.usage.kdl
  • src/cli/trust.rs
🚧 Files skipped from review as they are similar to previous changes (2)
  • docs/cli/trust.md
  • docs/faq.md

jdx and others added 2 commits June 12, 2026 22:27
is_safe_config_body only checked the raw TOML body for Tera delimiters,
so a value like "\u007b\u007b exec(...) \u007d\u007d" (which TOML
decodes to a real template) loaded without trust and executed via exec()
when the tool version or task field rendered. Re-check every decoded
string (keys and values, at any depth) for template syntax so escaped
delimiters can't slip through.

Also load safe (untrusted) mise.toml files through tracked-config reload
paths: get_tracked_config_files skipped any path without a trust marker,
which safe configs don't create, so their tool pins went missing from
`mise ls --all-sources`, `mise upgrade`, and `mise prune`.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@jdx

jdx commented Jun 12, 2026

Copy link
Copy Markdown
Owner Author

Thanks for the reviews — addressed in c70a4dc:

  • Escaped-template trust bypass (CodeRabbit, critical). Confirmed real — tiny = "{{ exec(...) }}" decoded to a live template and ran without trust. is_safe_config_body now recursively re-checks every decoded TOML string (keys + values, any depth) for template syntax, not just the raw body. Unit + e2e regressions added for the tools and tasks paths (and an escaped key).

  • Tracked safe configs skipped (Cursor, medium). Fixed — get_tracked_config_files now also loads safe trust-exempt configs (MiseToml::path_is_trust_exempt), so their tool pins remain visible to mise ls --all-sources / upgrade / prune. Regression test: e2e/config/test_trust_safe_config_tracked.

  • Trust exemption not shared / misleading "no tasks" error (Cursor, medium). The tasks commit (46e02fd) makes tasks-only configs load, so mise run <task> from a safe config now works instead of erroring; test_task_untrusted_config_error was updated to assert this and to keep the trust error for an unsafe ([env]) config.

  • Trust-marker assertion false negative (Greptile, P2). The e2e checks now assert no marker files/symlinks exist under trusted-configs (via find), instead of a bare directory-existence check.

  • [tools] array form in docs (CodeRabbit). Doc text (CLI help, FAQ, regenerated man/usage) now says "plain version strings (or arrays of them)".

  • Double TOML parse (Greptile, P2). Left as-is intentionally: the safety check parses to toml::Value and the main load deserializes into MiseToml (different targets), and the published hyperfine numbers show the load path within noise (≈1.03×). Happy to fold them into one parse if you'd prefer.

This comment was generated by an AI coding assistant.

@cursor cursor Bot left a comment

Copy link
Copy Markdown

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 1 potential issue.

Fix All in Cursor

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

Reviewed by Cursor Bugbot for commit c70a4dc. Configure here.

Comment thread src/config/mod.rs Outdated
task_include_requires_trust only scanned the raw file text, so a
mise-tasks/*.toml field or a #MISE script header written with escaped
delimiters (e.g. "\u007b\u007b exec(...) \u007d\u007d") decoded to a
real template after parsing and rendered — running exec() — during
`mise tasks` without trust. Now also reject template syntax found in the
decoded TOML: whole-file for .toml includes, and #MISE header values for
scripts (the only part parsed and rendered at load). Reuses the recursive
toml_value_has_template helper added for is_safe_config_body.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@jdx

jdx commented Jun 12, 2026

Copy link
Copy Markdown
Owner Author

Addressed the task-include variant in c01306d (Cursor, high — "Task includes skip decoded templates").

Confirmed it was real and reproduced it: a mise-tasks/ci.toml field and a #MISE script header both written with escaped delimiters ({{ exec(...) }}) decoded to live templates and executed during mise tasks without trust — the same class of bug as the is_safe_config_body one, but in task_include_requires_trust, which only scanned raw text.

Fix: task_include_requires_trust now also rejects template syntax found in the decoded TOML — the whole file for .toml includes, and the #MISE header values for scripts (the only part parsed and rendered at load). The #MISE header extraction is refactored out of Task::from_path into a shared parse_mise_header_toml so the trust check and the loader agree on exactly what gets parsed, and it reuses the recursive toml_value_has_template helper from the earlier fix.

Tests: unit test_file_has_decoded_template (both file types, plain + escaped) and e2e e2e/tasks/test_task_include_trust_escaped (non-paranoid; verifies escaped .toml and #MISE includes are blocked with nothing executing, and a plain file task still loads untrusted).

This comment was generated by an AI coding assistant.

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

Actionable comments posted: 2

🤖 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.

Inline comments:
In `@e2e/tasks/test_task_include_trust_escaped`:
- Around line 21-30: Update the test header to use escaped template delimiters
so the header contains an escaped literal template (not raw unescaped {{...}});
replace the current header string in mise-tasks/evil.sh (the line that sets
`#MISE` description="{{ exec(command='touch script-marker') }}") with an
escaped-brace form (e.g. backslash-escaped or otherwise escaped delimiters like
\{\{ ... \}\}) so the test exercises the script-header raw-text gate correctly
and fails if decoded headers stop being checked.

In `@src/task/mod.rs`:
- Around line 507-519: The regex in parse_mise_header_toml currently only
matches `#MISE`/`#[MISE]` without a space and thus misses `# MISE`, `// MISE`,
`:: MISE`; update the regex in parse_mise_header_toml so the comment marker
group (?:#|//|::) allows optional whitespace after it and the MISE token/bracket
form is matched with that spacing, keeping the TOML capture group intact (e.g.
use a pattern like
r"^(?:#|//|::)\s*(?:MISE|\[MISE\])\s+([a-z0-9_.-]+\s*=\s*[^\n]+)$") so
captures.extract().1 and the subsequent toml::de::from_str logic continue to
work.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 66bf2b5d-19ad-4f60-a9aa-b681d0a39823

📥 Commits

Reviewing files that changed from the base of the PR and between c70a4dc and c01306d.

📒 Files selected for processing (4)
  • e2e/tasks/test_task_include_trust_escaped
  • src/config/config_file/mise_toml.rs
  • src/config/mod.rs
  • src/task/mod.rs
🚧 Files skipped from review as they are similar to previous changes (1)
  • src/config/mod.rs

Comment thread e2e/tasks/test_task_include_trust_escaped Outdated
Comment thread src/task/mod.rs
Comment on lines +507 to +519
/// Parse the `#MISE key=value` (and `// MISE`, `:: MISE`, `[MISE]`) header
/// lines out of a task script into their decoded TOML values.
fn parse_mise_header_toml(body: &str) -> Result<Vec<toml::Value>> {
body.lines()
.filter_map(|line| {
regex!(r"^(?:#|//|::)(?:MISE| ?\[MISE\]) ([a-z0-9_.-]+\s*=\s*[^\n]+)$").captures(line)
})
.map(|captures| captures.extract().1)
.map(|[toml]| {
toml::de::from_str::<toml::Value>(toml)
.map_err(|e| eyre::eyre!("failed to parse task header TOML {toml:?}: {e}"))
})
.collect()

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.

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Allow whitespace before plain MISE headers.

Line 512 only matches #MISE and # [MISE]-style prefixes. It does not match # MISE / // MISE / :: MISE, so Task::from_path() now drops metadata for those header forms. That also contradicts the doc comment here and the existing test_from_path_toml_headers() case with # MISE alias="b".

Suggested fix
-            regex!(r"^(?:#|//|::)(?:MISE| ?\[MISE\]) ([a-z0-9_.-]+\s*=\s*[^\n]+)$").captures(line)
+            regex!(r"^(?:#|//|::)\s*(?:MISE|\[MISE\])\s+([a-z0-9_.-]+\s*=\s*[^\n]+)$").captures(line)
🤖 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/task/mod.rs` around lines 507 - 519, The regex in parse_mise_header_toml
currently only matches `#MISE`/`#[MISE]` without a space and thus misses `#
MISE`, `// MISE`, `:: MISE`; update the regex in parse_mise_header_toml so the
comment marker group (?:#|//|::) allows optional whitespace after it and the
MISE token/bracket form is matched with that spacing, keeping the TOML capture
group intact (e.g. use a pattern like
r"^(?:#|//|::)\s*(?:MISE|\[MISE\])\s+([a-z0-9_.-]+\s*=\s*[^\n]+)$") so
captures.extract().1 and the subsequent toml::de::from_str logic continue to
work.

The script-header case used a literal {{ template, which the raw-text gate
already rejects, so it didn't actually guard the decoded-header check. Use
escaped {{ delimiters so the test locks down file_has_decoded_template
for the #MISE script path.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@jdx

jdx commented Jun 12, 2026

Copy link
Copy Markdown
Owner Author

Two follow-ups from the latest review (1dd9a7e):

  • e2e/tasks/test_task_include_trust_escaped script-header case (correct, minor). Good catch — case 2 used a literal {{ template, so the raw-text gate rejected it and the decoded-#MISE-header path wasn't actually exercised. Switched it to escaped {{ delimiters via printf, so it now locks down file_has_decoded_template for the script path. (Case 1, the .toml include, already used escapes.)

  • parse_mise_header_toml header regex (src/task/mod.rs, flagged Major) — not a regression. The regex is a verbatim copy of the one previously inline in Task::from_path (r"^(?:#|//|::)(?:MISE| ?\[MISE\]) (...)$"), and test_from_path_toml_headers (which includes a # MISE alias="b" line) still passes, so header parsing behavior is unchanged by this refactor. If # MISE/// MISE/:: MISE (space-separated, no brackets) should be accepted, that's a pre-existing matter independent of this PR and best handled separately to avoid scope creep here.

This comment was generated by an AI coding assistant.

@jdx jdx merged commit f33e8fd into main Jun 12, 2026
35 checks passed
@jdx jdx deleted the feat/trust-safe-configs branch June 12, 2026 23:39
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