{"record":{"id":"82025f9ffec2a3f8","repo":"jdx/mise","slug":"brew-cask-refusing-generic-artifact-source-outsid-82025f","errorCode":null,"errorMessage":"brew-cask: refusing generic artifact source outside the extraction root: {}","messagePattern":"brew-cask: refusing generic artifact source outside the extraction root: (.+?)","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"src/system/packages/brew/cask/mod.rs","lineNumber":1555,"sourceCode":"        let Ok(relative) = source.strip_prefix(temporary_caskroom) else {\n            continue;\n        };\n        file::remove_file(&target)?;\n        create_flight_symlink(&final_caskroom.join(relative), &target, FlightSudo::Never)?;\n    }\n    Ok(())\n}\n\nfn install_generic_artifact(\n    stage: &Path,\n    temporary_caskroom: &Path,\n    artifact: &GenericArtifact,\n    targets: &mut FlightTargetTransaction,\n) -> Result<()> {\n    let source = find_artifact_matching(stage, &artifact.source, |_| true)\n        .ok_or_else(|| eyre!(\"brew-cask: artifact '{}' was not found\", artifact.source))?;\n    if !path_starts_with_resolved_root(&source, stage) {\n        bail!(\n            \"brew-cask: refusing generic artifact source outside the extraction root: {}\",\n            source.display()\n        );\n    }\n    let target = generic_artifact_target_path(&artifact.target)?;\n    // Not a lexical `strip_prefix`: the lookup resolves symlinks it had to\n    // traverse, so a source reached that way can be contained by the stage\n    // without sharing its literal prefix — as it is whenever `stage` itself\n    // has a symlinked ancestor. `staged_relative_path` retries against the\n    // resolved stage, matching the containment check above.\n    let relative_source = staged_relative_path(stage, &source).ok_or_else(|| {\n        eyre!(\n            \"brew-cask: generic artifact source is not contained by the extraction root: {}\",\n            source.display()\n        )\n    })?;\n    let caskroom_source = temporary_caskroom.join(relative_source);\n    if !path_starts_with_resolved_root(&caskroom_source, temporary_caskroom) {","sourceCodeStart":1537,"sourceCodeEnd":1573,"githubUrl":"https://github.com/jdx/mise/blob/afd2eddd3a50c16190efc1c7e94404b48f72af57/src/system/packages/brew/cask/mod.rs#L1537-L1573","documentation":"When installing a cask's generic artifact, mise locates the source file matching artifact.source inside the extraction (stage) root, then verifies with path_starts_with_resolved_root that the found path is actually resolved-contained within the stage. If the resolved location falls outside the extraction root (e.g. the lookup had to traverse a symlink pointing out of the tree), installation is refused to keep a cask from exfiltrating or overwriting files outside its own payload.","triggerScenarios":"Calling install_generic_artifact where find_artifact_matching resolves the cask's declared `source` glob/pattern to a path whose fully-resolved location is not under the stage directory. Typically caused by symlinked directories inside the extracted payload pointing out of the tree.","commonSituations":"A maliciously or accidentally crafted cask whose payload contains symlinks out of the stage; the artifact source pattern matching a symlinked path that resolves elsewhere; a stage directory placed under a symlinked path interacting with lookup resolution.","solutions":["Inspect the extracted stage for out-of-tree symlinks and remove/fix them before reinstalling","Re-download/re-extract the cask to rule out corruption or tampering","Ensure mise's caskroom/stage paths are not themselves behind unexpected symlinks","Report the cask upstream if the artifact genuinely lives outside the payload"],"exampleFix":null,"handlingStrategy":"validation","validationCode":"// verify the artifact source stays inside the stage after resolution\nlet resolved = dunce::canonicalize(stage.join(&artifact.source))?;\nif !resolved.starts_with(dunce::canonicalize(stage)?) {\n    return Err(format!(\"artifact source resolves outside stage: {}\", resolved.display()));\n}","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Keep cask payload symlinks pointing only within the payload tree","Avoid declaring artifact.source patterns that can match symlinked paths","Extract the stage on a filesystem without symlinked intermediates","Verify cask integrity (checksum) before extraction"],"tags":["security","symlink","brew-cask","containment"],"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-14T05:17:10.506Z"}