{"record":{"id":"76c67b62f58702ae","repo":"jdx/mise","slug":"brew-cask-staged-symlink-path-escaped-extraction","errorCode":null,"errorMessage":"brew-cask: staged symlink path escaped extraction root: {}","messagePattern":"brew-cask: staged symlink path escaped extraction root: (.+?)","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"critical","filePath":"src/system/packages/brew/cask.rs","lineNumber":1688,"sourceCode":"}\n\nfn copy_staged_artifact_closure(stage: &Path, owned_stage: &Path, source: &Path) -> Result<()> {\n    let stage = lexically_normalized_path(stage);\n    let mut pending = vec![lexically_normalized_path(source)];\n    let mut visited = BTreeSet::new();\n    while let Some(source) = pending.pop() {\n        let relative = staged_relative_path(&stage, &source).ok_or_else(|| {\n            eyre!(\n                \"brew-cask: staged symlink target escaped extraction root: {}\",\n                source.display()\n            )\n        })?;\n        if relative.components().next().is_some()\n            && !source\n                .parent()\n                .is_some_and(|parent| path_starts_with_resolved_root(parent, &stage))\n        {\n            bail!(\n                \"brew-cask: staged symlink path escaped extraction root: {}\",\n                source.display()\n            );\n        }\n        if !visited.insert(relative.to_path_buf()) {\n            continue;\n        }\n        let destination = owned_stage.join(&relative);\n        let metadata = source.symlink_metadata()?;\n        if destination.symlink_metadata().is_err() {\n            if let Some(parent) = destination.parent() {\n                file::create_dir_all(parent)?;\n            }\n            if metadata.file_type().is_symlink() {\n                file::make_symlink(&std::fs::read_link(&source)?, &destination)?;\n            } else {\n                copy_cask_artifact(&source, &destination)?;\n            }","sourceCodeStart":1670,"sourceCodeEnd":1706,"githubUrl":"https://github.com/jdx/mise/blob/6f52dcdf99e282ef7a7db68c81301fa4618d0f79/src/system/packages/brew/cask.rs#L1670-L1706","documentation":"While copying the staged tree into mise's private caskroom (src/system/packages/brew/cask.rs:1688), every path must resolve inside the extraction root. If staged_relative_path cannot express a source relative to the stage, or the source's parent is not under the resolved stage root, the payload contains a symlink that escapes the extraction directory, and mise refuses to follow it.","triggerScenarios":"An extracted cask archive containing absolute symlinks or links traversing '../../..' out of the extraction directory, so an installed artifact would point outside the staged tree — the classic hostile-payload shape this guard exists for.","commonSituations":"Malicious or repackaged third-party casks; archives built with unusual tooling that symlink to /tmp or system locations; taps whose artifacts were re-packed after tampering.","solutions":["Inspect the cask's payload yourself: download the cask URL, extract it, and run `find . -type l -ls` to see the escaping symlink","Treat this as a security boundary, not a bug — do not install casks from taps you do not trust","Prefer the official homebrew/cask build of the app","Report the offending cask to the tap maintainer"],"exampleFix":null,"handlingStrategy":"validation","validationCode":"# reject escaping symlinks before install\nfind \"$EXTRACT_DIR\" -type l -printf '%p -> %l' | grep -E '\\.\\.|^/' && { echo 'escaping symlink'; exit 1; }","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Only install casks from taps you trust — the escape check is a security boundary","Scan extracted archives in CI for absolute or ../ symlinks before republishing them as casks","Treat any hit of this error as a supply-chain incident, not a bug to work around"],"tags":["brew-cask","symlink","path-traversal","security"],"backgroundTag":"path-traversal-blocked","analyzedSha":"6f52dcdf99e282ef7a7db68c81301fa4618d0f79","analyzedAt":"2026-08-22T10:14:23.840Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}