{"record":{"id":"d00cb70fd5d1ee77","repo":"jdx/mise","slug":"option-path-must-be-a-safe-relative-path-n","errorCode":null,"errorMessage":"{option}: '{path}' must be a safe relative path (no absolute paths or parent directories)","messagePattern":"(.+?): '(.+?)' must be a safe relative path \\(no absolute paths or parent directories\\)","errorType":"validation","errorClass":"eyre::Report","httpStatus":null,"severity":"error","filePath":"src/backend/static_helpers.rs","lineNumber":1145,"sourceCode":"\n/// Rejects `bin`/`rename_exe` names that are not plain file names (`../tool`,\n/// `/abs/tool`, `bin/tool`), which would otherwise be joined onto the install\n/// or search directory and place the binary outside it.\npub fn ensure_plain_bin_name(option: &str, name: &str) -> eyre::Result<()> {\n    if !file::is_plain_file_name(name) {\n        bail!(\n            \"{option}: '{name}' must be a plain file name \\\n             (no path separators or parent directories)\"\n        );\n    }\n    Ok(())\n}\n\n/// Rejects a configured binary path that is absolute or contains parent\n/// components, while preserving the established `bin = \"bin/tool\"` form.\npub fn ensure_safe_relative_bin_path(option: &str, path: &str) -> eyre::Result<()> {\n    if !file::is_safe_relative_path(path) {\n        bail!(\n            \"{option}: '{path}' must be a safe relative path \\\n             (no absolute paths or parent directories)\"\n        );\n    }\n    Ok(())\n}\n\n/// Renames `path` (named `file_name`) to `target` within `dir`, preserving any\n/// required extension and ensuring the result is executable. A collision on the\n/// target (two mappings pointing at the same name, or the archive already\n/// containing that name) is unsatisfiable, so it fails the install loudly rather\n/// than silently dropping a binary and reporting success.\nfn finish_rename(dir: &Path, path: &Path, file_name: &str, target: &str) -> eyre::Result<()> {\n    let target_path = keep_required_extensions(dir, file_name, target, dir.join(target));\n    // Ensure the binary is executable whether or not we move it: ZIP archives drop\n    // the exec bit, and the file may already carry the desired name.\n    if !file::is_executable(path) {\n        file::make_executable(path)?;","sourceCodeStart":1127,"sourceCodeEnd":1163,"githubUrl":"https://github.com/jdx/mise/blob/9dcfcaa0dc8747a2577d3270b69bb9d8313b2807/src/backend/static_helpers.rs#L1127-L1163","documentation":"mise validates the `bin` (and `rename target`) option with `file::is_safe_relative_path`: it must be a non-empty relative path whose components are all normal (no leading `/`, no `C:` drive prefix, no `..` segments). This preserves the accepted `bin = \"bin/tool\"` form while rejecting absolute or parent-escaping paths that would resolve the binary outside the install directory. Thrown at option-parse time in static backends (src/backend/static_helpers.rs:587, :794, :900; src/backend/http.rs:405).","triggerScenarios":"Setting `bin = \"/usr/local/bin/tool\"`, `bin = \"../../usr/bin/tool\"`, `bin = \"C:\\\\tools\\\\tool.exe\"`, or `bin = \"\"` in a mise.toml tool entry backed by the http/ubi/static helpers. Any of these makes ensure_safe_relative_bin_path bail before the install proceeds.","commonSituations":"Users pasting an absolute system path into `bin` thinking it is the target location on disk; configs using Windows drive letters; values with trailing `..` from manually trimming a prefix; empty string left after template rendering.","solutions":["Make the value relative to the tool's install directory, e.g. bin = \"bin/tool\" or bin = \"tool\"","Remove leading slashes/backslashes and drive prefixes; replace `..` segments with the real relative location inside the archive","If you genuinely need an absolute path to an external binary, that is not what `bin` does — reference the executable via shims or PATH instead"],"exampleFix":"# before (mise.toml)\n[tools.http]\nmytool = { url = 'https://example.com/t.tar.gz', bin = '/usr/local/bin/mytool' }\n\n# after\n[tools.http]\nmytool = { url = 'https://example.com/t.tar.gz', bin = 'bin/mytool' }","handlingStrategy":"validation","validationCode":"# Rust: mirror of file::is_safe_relative_path\nfn is_safe_relative_bin(s: &str) -> bool {\n    if s.is_empty() { return false; }\n    let n = s.replace('\\\\', \"/\");\n    let b = n.as_bytes();\n    if n.starts_with('/') || (b.len() >= 2 && b[0].is_ascii_alphabetic() && b[1] == b':') {\n        return false;\n    }\n    n.components().all(|c| matches!(c, std::path::Component::Normal(_)))\n}\nassert!(is_safe_relative_bin(\"bin/tool\"));\nassert!(!is_safe_relative_bin(\"../tool\"));","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Write `bin` paths relative to the tool's install dir, never absolute","Avoid `..` and drive letters in mise.toml tool options","Test config with `mise install` in a scratch project before committing"],"tags":["mise","config-validation","path-traversal","backend","bin-path"],"backgroundTag":"config-validation-failed","analyzedSha":"9dcfcaa0dc8747a2577d3270b69bb9d8313b2807","analyzedAt":"2026-08-17T14:28:50.624Z","schemaVersion":2},"datasetVersion":"2026-08-21T13:17:26.733Z"}