{"record":{"id":"54b28c6179b62adf","repo":"jdx/mise","slug":"brew-cask-generic-artifact-source-is-not-contained-by-the","errorCode":null,"errorMessage":"brew-cask: generic artifact source is not contained by the extraction root: {}","messagePattern":"brew-cask: generic artifact source is not contained by the extraction root: (.+?)","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"src/system/packages/brew/cask/mod.rs","lineNumber":1791,"sourceCode":"            \"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) {\n        bail!(\n            \"brew-cask: refusing to stage generic artifact through a path outside the caskroom: {}\",\n            caskroom_source.display()\n        );\n    }\n    #[cfg(not(unix))]\n    if let Some(parent) = target.parent() {\n        file::create_dir_all(parent)?;\n    }\n    let elevated_target = targets.protect_generic(&target)?;\n    copy_generic_artifact(&source, &target, elevated_target.as_deref())?;\n    if let Some(parent) = caskroom_source.parent() {\n        file::create_dir_all(parent)?;\n    }\n    file::make_symlink(&target, &caskroom_source)?;\n    targets.record_installed(target);\n    Ok(())\n}\n","sourceCodeStart":1773,"sourceCodeEnd":1809,"githubUrl":"https://github.com/jdx/mise/blob/533346cc374382b41ec5ff70536252b2e96e725c/src/system/packages/brew/cask/mod.rs#L1773-L1809","documentation":"During generic-artifact cask installation, mise locates the artifact inside the extraction stage and computes its path relative to the stage root. If the resolved source path cannot be expressed as a path strictly contained by the extraction root (including via resolved symlinks), installation aborts. This is a path-containment safety check that prevents a cask-defined `source` glob from matching files outside the extracted payload.","triggerScenarios":"Installing a cask with a `generic` artifact whose `source` resolves (after `find_artifact_matching` traverses symlinks) to a location outside the extraction stage; also triggered when the stage directory itself is behind a symlinked ancestor so lexical and resolved prefixes diverge and `staged_relative_path` fails against both.","commonSituations":"A cask stanza lists a source path that the package actually places outside the stage (mis-authored or changed upstream cask); a user's temp directory is a symlink (/tmp -> /private/tmp) interacting with stage path resolution; tampered or modified cask definitions.","solutions":["Update or fix the cask definition so `source` matches only paths inside the extracted artifact","Re-download the cask payload to rule out a corrupted or altered stage","Check for symlinked ancestors of the mise temp/stage directories and use a real path if a local workaround is needed","Report the cask to the mise/homebrew maintainers if a stock cask triggers this"],"exampleFix":"// cask generic artifact before (source escapes the stage)\n// source: \"/Applications/../..\" \n// after\n// source: \"App.app\" (relative to extraction root)","handlingStrategy":"validation","validationCode":"let stage = fs::canonicalize(stage_dir)?;\nlet source = fs::canonicalize(stage_dir.join(cask_source))?;\nif !source.starts_with(&stage) {\n    return Err(format!(\"artifact source {source:?} escapes stage {stage:?}\"));\n}","typeGuard":"fn is_contained(child: &Path, root: &Path) -> bool {\n    std::fs::canonicalize(child).map(|c| c.starts_with(root)).unwrap_or(false)\n}","tryCatchPattern":null,"preventionTips":["Keep cask artifact sources relative to the extraction root","Avoid symlinked temp directories when installing casks","Re-fetch casks instead of hand-editing stanzas"],"tags":["security","path-traversal","brew-cask","path-validation"],"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"}