{"record":{"id":"995f7f5ffe1c86e0","repo":"jdx/mise","slug":"brew-cask-kind-name-must-stay-below-applica","errorCode":null,"errorMessage":"brew-cask: {kind} '{name}' must stay below Applications","messagePattern":"brew-cask: (.+?) '(.+?)' must stay below Applications","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"src/system/packages/brew/cask/mod.rs","lineNumber":3365,"sourceCode":"    let relative = source.strip_prefix(final_caskroom).ok()?;\n    if relative\n        .components()\n        .any(|component| matches!(component, Component::ParentDir))\n    {\n        return None;\n    }\n    Some(root.join(relative))\n}\n\n/// Rejects an empty relative path and any component that could climb out of\n/// `$APPDIR` (`..`, a root, a prefix).\nfn reject_appdir_escape(relative: &Path, kind: &str, name: &str) -> Result<()> {\n    if relative.components().next().is_none()\n        || relative\n            .components()\n            .any(|component| !matches!(component, Component::Normal(_)))\n    {\n        bail!(\"brew-cask: {kind} '{name}' must stay below Applications\");\n    }\n    Ok(())\n}\n\nfn resolve_symlink_target(link: &Path, target: PathBuf) -> PathBuf {\n    if target.is_absolute() {\n        target\n    } else {\n        link.parent()\n            .map(|parent| parent.join(&target))\n            .unwrap_or(target)\n    }\n}\n\nfn durable_internal_symlink_source(stage: &Path, source: &Path, link: &Path) -> Option<PathBuf> {\n    if !source.is_absolute()\n        || staged_relative_path(stage, source).is_none()\n        || staged_relative_path(stage, link).is_none()","sourceCodeStart":3347,"sourceCodeEnd":3383,"githubUrl":"https://github.com/jdx/mise/blob/afd2eddd3a50c16190efc1c7e94404b48f72af57/src/system/packages/brew/cask/mod.rs#L3347-L3383","documentation":"reject_appdir_escape validates that app/artifact target paths stay strictly beneath the Applications directory: the relative path must be non-empty and composed only of normal components. Empty paths or paths containing '..', '.', root, or prefix components are rejected.","triggerScenarios":"Installing a cask app (or similar kind) whose resolved target name is empty, absolute, or contains non-Normal components such as `..`, `/`, or a drive/root prefix.","commonSituations":"A cask stanza targeting a location outside /Applications (e.g. `../Utilities/Foo.app` or an absolute `/Library/Foo.app`), or a target template that expanded to an empty string.","solutions":["Set the app target to a simple relative name like `Foo.app` that lives directly under Applications","Remove `..`, absolute, or special path components from the target","Check the upstream Homebrew cask for the canonical target name"],"exampleFix":"// before\ntarget = \"../Utilities/Foo.app\"\n// after\ntarget = \"Foo.app\"","handlingStrategy":"validation","validationCode":"let rel = Path::new(target_name);\nlet ok = !target_name.is_empty()\n    && rel.is_relative()\n    && rel.components().all(|c| matches!(c, Component::Normal(_)));\nif !ok {\n    return Err(\"app target must be a simple path under Applications\");\n}","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Keep app targets as plain 'Name.app' relative names","Reject absolute paths and '..' in artifact targets during parsing"],"tags":["brew-cask","path-validation","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"}