{"record":{"id":"77abecf29da5c6e3","repo":"jdx/mise","slug":"brew-cask-refusing-to-stage-cask-payload-through","errorCode":null,"errorMessage":"brew-cask: refusing to stage cask payload through a path outside the caskroom: {}","messagePattern":"brew-cask: refusing to stage cask payload through a path outside the caskroom: (.+?)","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"src/system/packages/brew/cask/mod.rs","lineNumber":3110,"sourceCode":"        .iter()\n        .filter_map(|app| find_app(stage, &app.source))\n        .map(|source| file::desymlink_path(&source))\n        .collect();\n    for entry in std::fs::read_dir(stage)? {\n        let source = entry?.path();\n        if !path_starts_with_resolved_root(&source, stage) {\n            continue;\n        }\n        let resolved = file::desymlink_path(&source);\n        if app_sources.contains(&resolved) {\n            continue;\n        }\n        let Some(relative) = staged_relative_path(stage, &source) else {\n            continue;\n        };\n        let target = caskroom.join(&relative);\n        if !path_starts_with_resolved_root(&target, caskroom) {\n            bail!(\n                \"brew-cask: refusing to stage cask payload through a path outside the caskroom: {}\",\n                target.display()\n            );\n        }\n        if target.symlink_metadata().is_ok() {\n            continue;\n        }\n        if let Some(parent) = target.parent() {\n            file::create_dir_all(parent)?;\n        }\n        if source.is_dir() {\n            file::copy_dir_all_preserve_symlinks(&source, &target)?;\n        } else {\n            file::copy(&source, &target)?;\n        }\n    }\n    Ok(())\n}","sourceCodeStart":3092,"sourceCodeEnd":3128,"githubUrl":"https://github.com/jdx/mise/blob/afd2eddd3a50c16190efc1c7e94404b48f72af57/src/system/packages/brew/cask/mod.rs#L3092-L3128","documentation":"A security guard during cask payload staging: the computed destination (caskroom joined with the staged relative path) does not resolve to a location inside the caskroom, likely due to symlink traversal or '..' components. mise bails instead of writing outside the caskroom.","triggerScenarios":"Staging a cask payload file whose relative path escapes the caskroom via '..' components or a symlink inside the caskroom pointing elsewhere, detected by path_starts_with_resolved_root before any write.","commonSituations":"Crafted or corrupted cask archives containing path-traversal entries, a previously created symlink in the caskroom redirected at another directory, or a stage root itself being a symlink into a different tree.","solutions":["Remove any '..' components or symlink indirection from the staged payload paths","Inspect the caskroom for pre-existing symlinks pointing outside it and delete them","Re-extract the cask archive from a trusted source and retry the install"],"exampleFix":"// before\nrelative = \"../../etc/foo\" // escapes caskroom\n// after\nrelative = \"scripts/foo\" // stays under caskroom","handlingStrategy":"validation","validationCode":"let target = caskroom.join(&relative);\nif !target.starts_with(&caskroom) || relative.components().any(|c| c == Component::ParentDir) {\n    return Err(\"payload path escapes the caskroom\");\n}","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Reject any payload entry containing '..' at archive extraction time","Avoid placing symlinks inside the caskroom that point outside it","Verify stage and caskroom roots are real directories, not symlinked into other trees"],"tags":["brew-cask","path-traversal","security"],"backgroundTag":"path-traversal-blocked","analyzedSha":"afd2eddd3a50c16190efc1c7e94404b48f72af57","analyzedAt":"2026-09-09T01:38:25.179Z","contentChangedAt":"2026-09-09T01:38:25.179Z","schemaVersion":2},"datasetVersion":"2026-09-14T00:17:10.932Z"}