{"record":{"id":"641245a542f670b1","repo":"jdx/mise","slug":"path-must-not-escape-the-task-directory","errorCode":null,"errorMessage":"path must not escape the task directory","messagePattern":"path must not escape the task directory","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"src/task/task_cache.rs","lineNumber":1261,"sourceCode":"            )\n        })?;\n    }\n    build_output_matcher(root, &patterns)\n        .wrap_err_with(|| format!(\"task {} has an invalid cache output pattern\", task.name))?;\n    Ok(())\n}\n\nfn ensure_safe_relative(path: &Path) -> Result<()> {\n    if path.as_os_str().is_empty() || path.is_absolute() {\n        bail!(\"path must be a non-empty relative path\");\n    }\n    if path.components().any(|c| {\n        matches!(\n            c,\n            Component::ParentDir | Component::RootDir | Component::Prefix(_)\n        )\n    }) {\n        bail!(\"path must not escape the task directory\");\n    }\n    if !path.components().any(|c| matches!(c, Component::Normal(_))) {\n        bail!(\"path must identify an output beneath the task directory\");\n    }\n    Ok(())\n}\n\nfn resolve_output_roots(task: &Task, root: &Path, require_matches: bool) -> Result<Vec<PathBuf>> {\n    let mut resolved = BTreeSet::new();\n    let patterns = task.outputs.patterns();\n    let matcher = build_output_matcher(root, &patterns)?;\n    for output in output_glob_patterns(&patterns) {\n        ensure_safe_relative(Path::new(&output))?;\n        if crate::task::task_source_checker::is_glob_pattern(&output) {\n            let mut glob_matched = false;\n            for expanded in expand_enumeration_patterns(&output)? {\n                ensure_safe_relative(Path::new(&expanded))?;\n                for entry in glob_walk(&root.join(expanded), false)? {","sourceCodeStart":1243,"sourceCodeEnd":1279,"githubUrl":"https://github.com/jdx/mise/blob/533346cc374382b41ec5ff70536252b2e96e725c/src/task/task_cache.rs#L1243-L1279","documentation":"mise validates task cache output paths with ensure_safe_relative, which rejects any relative path containing ParentDir (..), RootDir (/), or Windows Prefix components. This prevents a task's cache outputs from referencing or overwriting files outside the task's working directory. It is a deliberate path-traversal guard applied to each `outputs` entry in a task definition.","triggerScenarios":"Declaring a task output like `outputs = ['../build']`, an absolute path like `/tmp/out`, or a Windows drive path (`C:\\out`) in a task with cache outputs; the check runs when validating/resolving task cache outputs.","commonSituations":"Authors moving outputs to a shared directory outside the task dir; copying an absolute path from a build log; Windows users using drive-letter paths; typos like `..foo/..`.","solutions":["Make the output path relative and inside the task directory (e.g. `dist/` instead of `../dist`)","If outputs genuinely live outside the task dir, relocate them into the task dir or change the task's dir","Remove leading `/` or drive prefixes and any `..` components from the pattern"],"exampleFix":"# before\n[task.build]\noutputs = ['../shared/dist']\n# after\n[task.build]\noutputs = ['dist']","handlingStrategy":"validation","validationCode":"fn is_safe_output(p: &str) -> bool {\n    let path = std::path::Path::new(p);\n    !path.is_empty() && !path.is_absolute()\n        && !path.components().any(|c| matches!(c,\n            std::path::Component::ParentDir\n            | std::path::Component::RootDir\n            | std::path::Component::Prefix(_)))\n}","typeGuard":"fn safe_output(p: &str) -> Option<&str> {\n    is_safe_output(p).then_some(p)\n}","tryCatchPattern":null,"preventionTips":["Always declare outputs relative to the task's dir","Never start output paths with / or a drive letter","Avoid .. in generated output lists"],"tags":["path","validation","task-cache"],"backgroundTag":"path-traversal-blocked","analyzedSha":"533346cc374382b41ec5ff70536252b2e96e725c","analyzedAt":"2026-09-17T13:35:38.149Z","contentChangedAt":"2026-09-17T13:35:38.149Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}