{"record":{"id":"f85a7483778c81ce","repo":"jdx/mise","slug":"brew-cask-app-target-target-name-must-not-con-f85a74","errorCode":null,"errorMessage":"brew-cask: app target '{target_name}' must not contain '..'","messagePattern":"brew-cask: app target '(.+?)' must not contain '\\.\\.'","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"src/system/packages/brew/cask/paths.rs","lineNumber":124,"sourceCode":"            return false;\n        }\n    }\n    true\n}\n\npub(super) fn app_target_path(target_name: &str) -> Result<PathBuf> {\n    let app_dir = target_app_dir()?;\n    if target_name.contains('\\0') {\n        bail!(\"brew-cask: app target contains NUL\");\n    }\n    if target_name.contains('/') {\n        let target = target_name.replace(\"$HOMEBREW_PREFIX\", &prefix::prefix().to_string_lossy());\n        let path = PathBuf::from(target);\n        if path\n            .components()\n            .any(|component| matches!(component, Component::ParentDir))\n        {\n            bail!(\"brew-cask: app target '{target_name}' must not contain '..'\");\n        }\n        if path.is_absolute() {\n            let prefix_app_dir = prefix::prefix().join(\"Applications\");\n            if path.starts_with(&app_dir) || path.starts_with(&prefix_app_dir) {\n                return Ok(path);\n            }\n            // Casks routinely hardcode an absolute `/Applications/Foo.app`\n            // target. When an override appdir is configured, relocate such a\n            // target into it (preserving any subdirectories) rather than\n            // rejecting it. `$HOMEBREW_PREFIX`-anchored targets are handled by\n            // the check above and are never relocated.\n            if app_dir != Path::new(DEFAULT_APP_DIR)\n                && let Ok(rest) = path.strip_prefix(DEFAULT_APP_DIR)\n            {\n                return Ok(app_dir.join(rest));\n            }\n            bail!(\n                \"brew-cask: app target '{target_name}' must be under {}\",","sourceCodeStart":106,"sourceCodeEnd":142,"githubUrl":"https://github.com/jdx/mise/blob/afd2eddd3a50c16190efc1c7e94404b48f72af57/src/system/packages/brew/cask/paths.rs#L106-L142","documentation":"`app_target_path` validates a cask `app` artifact's target path before mise links it into the applications directory. Targets that contain a `/` are parsed as absolute paths, and any `..` component would let the target escape the app-dir containment boundary (symlinks here may be created with elevated privileges). mise rejects such targets with this error as a path-traversal safeguard.","triggerScenarios":"Calling cask install/validation (installed_skip_reason, install_app, app_target_paths, validate_adoptable_apps, appdir_artifact_source) with a cask whose `app target:` string contains `/` and a `..` component, e.g. `['Foo.app', '../../usr/bin']` or a malicious/edited cask stanza.","commonSituations":"Hand-edited or third-party cask stanzas with relative escape targets; typos in custom cask definitions; attempting to point an app target outside /Applications via `..` instead of an absolute path; copied cask from an untrusted source.","solutions":["Remove any `..` components from the cask's `app target:` value and use a plain app-bundle name (e.g. `Foo.app`) or an absolute path under the app dir","If the target should live elsewhere, set an absolute override directory via the brew-cask APP_DIR env var (validated separately) instead of using `..`","If the cask comes from upstream, verify it is the official definition and update the cask (`mise` refresh / brew update) so the malformed stanza is replaced","For custom casks, rewrite the target as an absolute path starting with `/Applications/` or `$HOMEBREW_PREFIX/`"],"exampleFix":"// before (cask stanza)\napp target: ['Foo.app', '../Utilities/Bar.app']\n// after\napp target: ['Foo.app', '/Applications/Utilities/Bar.app']","handlingStrategy":"validation","validationCode":"fn app_target_ok(target: &str) -> bool {\n    !target.contains('\\0')\n        && (!target.contains('/') || {\n            let p = std::path::Path::new(target.replace(\"$HOMEBREW_PREFIX\", \"\").as_str());\n            !p.components().any(|c| matches!(c, std::path::Component::ParentDir))\n        })\n}","typeGuard":"fn is_safe_cask_target(t: &str) -> bool {\n    !t.contains('\\0') && std::path::Path::new(t).components().all(|c| !matches!(c, std::path::Component::ParentDir))\n}","tryCatchPattern":"match cask.install() {\n    Err(e) if e.to_string().contains(\"must not contain '..'\") => {\n        eprintln!(\"fix the cask target: remove '..' components\");\n    }\n    r => r?,\n}","preventionTips":["Never write `..` in cask `app target:` values","Prefer bare bundle names and let mise place them in the app dir","Review third-party/custom cask stanzas for traversal-looking targets before installing","Test custom casks with a path lint before real installs"],"tags":["path-traversal","validation","brew-cask","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"}