Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions docs/cli/trust.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,13 @@ parsing `mise.toml`. Without trust, mise may prompt, skip the config in some
discovery paths, fail with an untrusted-config error when it cannot prompt,
or assume trust in detected CI unless paranoid mode is enabled.

Safe config files do not require trust: files that only contain
`min_version`, `[tools]` entries with plain version strings (or arrays
of them), and `[tasks]` (no templates and no tool options) are loaded
without prompting, since nothing in them executes code at load time —
tools install and tasks run only on explicit commands like `mise install`
or `mise run`.

## Arguments

### `[CONFIG_FILE]`
Expand Down
7 changes: 6 additions & 1 deletion docs/faq.md
Original file line number Diff line number Diff line change
Expand Up @@ -241,7 +241,12 @@ mise upgrade --bump node

## My config file is being ignored / `mise trust` issues

mise requires you to trust config files that were not created by you. Common issues:
mise requires you to trust config files that were not created by you. Safe config files —
those that only contain `min_version`, `[tools]` entries with plain version strings (or
arrays of them), and `[tasks]` (no templates and no tool options) — are loaded without trust, since nothing in
them executes code at load time: tools install and tasks run only on explicit commands like
`mise install` or `mise run`. Everything else (env vars, hooks, settings, aliases, templates,
tool options) requires trust. Common issues:

- **Accidentally denied trust**: If mise prompted you to trust a file and you said no, it gets
added to the ignore list. Check the `ignored-configs` directory in your
Expand Down
112 changes: 112 additions & 0 deletions e2e/config/test_trust_safe_config
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
#!/usr/bin/env bash

# Safe mise.toml files (min_version, [tools] with plain version strings, and
# [tasks]) can be loaded without trusting them first. Anything that can execute
# code at load time or change mise's behavior still requires trust.

export MISE_TRUSTED_CONFIG_PATHS=""
unset CI GITHUB_ACTIONS GITHUB_ACTION 2>/dev/null || true

# Fail if any trust marker file/symlink exists under trusted-configs. This is
# precise (a stale empty dir won't mask a wrongly-written marker) unlike a bare
# directory-existence check.
assert_no_trust_marker() {
local found
found=$(find "$MISE_STATE_DIR/trusted-configs" -mindepth 1 \( -type f -o -type l \) 2>/dev/null || true)
[[ -z $found ]] || fail "$1 (found trust markers: $found)"
}

mkdir -p project
cd project || exit 1

cat <<'EOF' >mise.toml
min_version = "2024.1.1"

[tools]
tiny = "3.1.0"

[tasks.hi]
run = "echo task-ran-without-trust"
EOF

# loads without prompting and without creating a trust marker
MISE_YES=0 mise install tiny
assert_contains "MISE_YES=0 mise ls tiny" "3.1.0"
Comment thread
greptile-apps[bot] marked this conversation as resolved.
assert_no_trust_marker "safe config should not be silently trusted"

# config tasks only execute on explicit `mise run`, so they don't need trust
assert_contains "MISE_YES=0 mise run hi" "task-ran-without-trust"

# template-free file tasks are inert at load time and runnable without trust
mkdir -p mise-tasks
cat <<'EOF' >mise-tasks/filetask
#!/usr/bin/env bash
#MISE description="a plain file task"
echo file-task-ran-without-trust
EOF
chmod +x mise-tasks/filetask
assert_contains "MISE_YES=0 mise run filetask" "file-task-ran-without-trust"
assert_no_trust_marker "file task should not require or create trust"

# a template in a file task header renders at load time, so it requires trust
cat <<'EOF' >mise-tasks/evil
#!/usr/bin/env bash
#MISE description="{{ exec(command='echo PWNED > marker') }}"
echo evil
EOF
chmod +x mise-tasks/evil
output=$(MISE_YES=0 mise tasks 2>&1 || true)
[[ ! -f marker ]] || fail "file task header template executed without trust"
echo "$output" | grep -qi "trust" || fail "expected trust error for templated file task, got: $output"
rm mise-tasks/evil

# a template in a version string requires trust and must not execute
cat <<'EOF' >mise.toml
[tools]
tiny = "{{ exec(command='echo PWNED > marker') }}"
EOF
output=$(MISE_YES=0 mise ls 2>&1 || true)
[[ ! -f marker ]] || fail "template executed in untrusted config"
echo "$output" | grep -qi "trust" || fail "expected trust error for template, got: $output"

# a template in a task renders at load time, so it requires trust
cat <<'EOF' >mise.toml
[tasks.hi]
run = "echo hi"
description = "{{ exec(command='echo PWNED > marker') }}"
EOF
output=$(MISE_YES=0 mise tasks 2>&1 || true)
[[ ! -f marker ]] || fail "task template executed in untrusted config"
echo "$output" | grep -qi "trust" || fail "expected trust error for task template, got: $output"

# escaped Tera delimiters ({ == '{', } == '}') decode to a template
# after TOML parsing; this must not bypass the safety check and exec
printf '[tools]\ntiny = "\\u007b\\u007b exec(command=%stouch marker%s) \\u007d\\u007d"\n' "'" "'" >mise.toml
output=$(MISE_YES=0 mise ls 2>&1 || true)
[[ ! -f marker ]] || fail "escaped template executed in untrusted config (tools)"
echo "$output" | grep -qi "trust" || fail "expected trust error for escaped tools template, got: $output"

printf '[tasks.hi]\nrun = "echo hi"\ndescription = "\\u007b\\u007b exec(command=%stouch marker%s) \\u007d\\u007d"\n' "'" "'" >mise.toml
output=$(MISE_YES=0 mise tasks 2>&1 || true)
[[ ! -f marker ]] || fail "escaped template executed in untrusted config (tasks)"
echo "$output" | grep -qi "trust" || fail "expected trust error for escaped task template, got: $output"

# tool options can run code (postinstall) or alter installs (install_env)
cat <<'EOF' >mise.toml
[tools]
tiny = { version = "3.1.0", postinstall = "touch marker" }
EOF
output=$(MISE_YES=0 mise ls 2>&1 || true)
echo "$output" | grep -qi "trust" || fail "expected trust error for tool options, got: $output"

# env vars require trust
cat <<'EOF' >mise.toml
[env]
FOO = "bar"
EOF
output=$(MISE_YES=0 mise env 2>&1 || true)
echo "$output" | grep -qi "trust" || fail "expected trust error for env, got: $output"

# unsafe configs still load normally once trusted
mise trust
assert_contains "mise env" "FOO=bar"
23 changes: 23 additions & 0 deletions e2e/config/test_trust_safe_config_tracked
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
#!/usr/bin/env bash

# A safe (untrusted) mise.toml is tracked and its tool pins remain visible
# through tracked-config reload paths like `mise ls --all-sources`, even when
# invoked from a different directory. Regression: tracked-config loading used
# to skip any config without a trust marker, which safe configs don't create.

export MISE_TRUSTED_CONFIG_PATHS=""
unset CI GITHUB_ACTIONS GITHUB_ACTION 2>/dev/null || true

mkdir -p proj other
cat <<'EOF' >proj/mise.toml
[tools]
tiny = "3.1.0"
EOF

# install from the safe config (loads + tracks it, no trust prompt/marker)
(cd proj && MISE_YES=0 mise install tiny)

# from an unrelated directory, the tracked safe config's pin is still listed
cd other || exit 1
assert_contains "MISE_YES=0 mise ls --all-sources 2>&1" "proj/mise.toml"
assert_contains "MISE_YES=0 mise ls --all-sources 2>&1" "tiny"
42 changes: 42 additions & 0 deletions e2e/tasks/test_task_include_trust_escaped
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
#!/usr/bin/env bash

# Task include files (mise-tasks/*.toml and #MISE script headers) that hide a
# Tera template behind escaped delimiters ({ -> '{', } -> '}') must
# still require trust: the escapes decode to a real template that renders — and
# can exec() — at load time. A raw-text scan alone would miss them. This is the
# non-paranoid case (paranoid mode trivially requires trust for everything).

export MISE_TRUSTED_CONFIG_PATHS=""
unset CI GITHUB_ACTIONS GITHUB_ACTION 2>/dev/null || true

mkdir -p mise-tasks

# 1. escaped template in a .toml task file
printf '[evil]\nrun = "echo hi"\ndescription = "\\u007b\\u007b exec(command=%stouch toml-marker%s) \\u007d\\u007d"\n' "'" "'" >mise-tasks/ci.toml
output=$(MISE_YES=0 mise tasks 2>&1 || true)
[[ ! -f toml-marker ]] || fail "escaped template executed from untrusted .toml task include"
echo "$output" | grep -qi "not trusted" || fail "expected trust error for escaped .toml include, got: $output"
rm -f mise-tasks/ci.toml

# 2. escaped template in a #MISE script header (printf so the \u escapes reach
# the file literally, exercising the decoded-header path rather than the
# raw-text gate)
{
printf '#!/usr/bin/env bash\n'
printf '#MISE description="\\u007b\\u007b exec(command=%stouch script-marker%s) \\u007d\\u007d"\n' "'" "'"
printf 'echo hi\n'
} >mise-tasks/evil.sh
chmod +x mise-tasks/evil.sh
output=$(MISE_YES=0 mise tasks 2>&1 || true)
[[ ! -f script-marker ]] || fail "escaped template executed from untrusted script header"
echo "$output" | grep -qi "not trusted" || fail "expected trust error for escaped script header, got: $output"
rm -f mise-tasks/evil.sh

# 3. sanity: a plain (template-free) file task still loads without trust
cat <<'EOF' >mise-tasks/hello.sh
#!/usr/bin/env bash
#MISE description="a plain task"
echo plain-task-ran
EOF
chmod +x mise-tasks/hello.sh
assert_contains "MISE_YES=0 mise run hello" "plain-task-ran"
8 changes: 6 additions & 2 deletions e2e/tasks/test_task_monorepo_trust
Original file line number Diff line number Diff line change
Expand Up @@ -43,10 +43,14 @@ assert_not_contains "mise tasks ls --all 2>&1" "Trust them"
assert_contains "mise run '//projects/frontend:build'" "frontend build"
assert_contains "mise run '//projects/frontend/components:test'" "component test"

# Test that trust warnings DO appear for configs outside monorepo
# Create a parent directory with its own config (not part of monorepo)
# Test that trust warnings DO appear for unsafe configs outside monorepo
# (tasks-only configs are safe and load without trust, so use [env] to make
# this one require trust)
mkdir -p ../parent-dir
cat <<EOF >../parent-dir/mise.toml
[env]
FOO = "bar"

[tasks.parent-task]
run = 'echo "parent task"'
EOF
Expand Down
19 changes: 15 additions & 4 deletions e2e/tasks/test_task_untrusted_config_error
Original file line number Diff line number Diff line change
Expand Up @@ -5,18 +5,29 @@

# Ensure we start with a clean slate - no trusted configs
export MISE_TRUSTED_CONFIG_PATHS=""
unset CI GITHUB_ACTIONS GITHUB_ACTION 2>/dev/null || true

# Create a config file with a task
# A config that only defines tasks is safe: tasks execute nothing until the
# user explicitly runs one, so no trust is required
cat <<EOF >mise.toml
[tasks.make]
run = "echo 'hello from task'"
EOF

# Running a task from an untrusted config should give a helpful error
# It should either:
assert_contains "MISE_YES=0 mise run make" "hello from task"

# Unsafe configs (here: [env]) still require trust. Running a task from one
# should give a helpful error message:
# 1. Prompt the user to trust the config (when interactive), or
# 2. Show a clear "config not trusted" error (not "no tasks defined")
#
cat <<EOF >mise.toml
[env]
FOO = "bar"

[tasks.make]
run = "echo 'hello from task'"
EOF

# This test verifies we don't get the misleading "no tasks defined" error
output=$(MISE_YES=0 mise run make 2>&1 || true)

Expand Down
7 changes: 7 additions & 0 deletions man/man1/mise.1
Original file line number Diff line number Diff line change
Expand Up @@ -3402,6 +3402,13 @@ that may execute code or affect the environment. mise checks trust before
parsing `mise.toml`. Without trust, mise may prompt, skip the config in some
discovery paths, fail with an untrusted\-config error when it cannot prompt,
or assume trust in detected CI unless paranoid mode is enabled.

Safe config files do not require trust: files that only contain
`min_version`, `[tools]` entries with plain version strings (or arrays
of them), and `[tasks]` (no templates and no tool options) are loaded
without prompting, since nothing in them executes code at load time —
tools install and tasks run only on explicit commands like `mise install`
or `mise run`.
.PP
\fBUsage:\fR mise trust [OPTIONS] [<CONFIG_FILE>]
.PP
Expand Down
7 changes: 7 additions & 0 deletions mise.usage.kdl
Original file line number Diff line number Diff line change
Expand Up @@ -3503,6 +3503,13 @@ that may execute code or affect the environment. mise checks trust before
parsing `mise.toml`. Without trust, mise may prompt, skip the config in some
discovery paths, fail with an untrusted-config error when it cannot prompt,
or assume trust in detected CI unless paranoid mode is enabled.

Safe config files do not require trust: files that only contain
`min_version`, `[tools]` entries with plain version strings (or arrays
of them), and `[tasks]` (no templates and no tool options) are loaded
without prompting, since nothing in them executes code at load time —
tools install and tasks run only on explicit commands like `mise install`
or `mise run`.
"""#
after_long_help #"""
Examples:
Expand Down
7 changes: 7 additions & 0 deletions src/cli/trust.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,13 @@ use itertools::Itertools;
/// parsing `mise.toml`. Without trust, mise may prompt, skip the config in some
/// discovery paths, fail with an untrusted-config error when it cannot prompt,
/// or assume trust in detected CI unless paranoid mode is enabled.
///
/// Safe config files do not require trust: files that only contain
/// `min_version`, `[tools]` entries with plain version strings (or arrays
/// of them), and `[tasks]` (no templates and no tool options) are loaded
/// without prompting, since nothing in them executes code at load time —
/// tools install and tasks run only on explicit commands like `mise install`
/// or `mise run`.
#[derive(Debug, clap::Args)]
#[clap(verbatim_doc_comment, after_long_help = AFTER_LONG_HELP)]
pub struct Trust {
Expand Down
Loading
Loading