{"record":{"id":"a245685b206cbd0c","repo":"jdx/mise","slug":"brew-cask-app-target-target-name-must-be-unde","errorCode":null,"errorMessage":"brew-cask: app target '{target_name}' must be under {}","messagePattern":"brew-cask: app target '(.+?)' must be under (.+?)","errorType":"validation","errorClass":"eyre::Report","httpStatus":null,"severity":"error","filePath":"src/system/packages/brew/cask.rs","lineNumber":6006,"sourceCode":"        {\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 {}\",\n                app_dir.display()\n            );\n        }\n        bail!(\"brew-cask: app target '{target_name}' must be an absolute path\");\n    }\n    Ok(app_dir.join(target_name))\n}\n\n/// The directory `app` artifacts are linked into: `/Applications` unless\n/// [`APP_DIR_ENV`] overrides it.\n///\n/// The override is validated here rather than at the point of use because\n/// `app_target_path` treats the result as a containment boundary for symlinks\n/// that may be created with elevated privileges. An empty value falls back to\n/// the default so that exporting `MISE_BREW_CASK_OPT_APPDIR=` cannot disable\n/// that boundary: `Path::starts_with(\"\")` is true for every path.\nfn target_app_dir() -> Result<PathBuf> {","sourceCodeStart":5988,"sourceCodeEnd":6024,"githubUrl":"https://github.com/jdx/mise/blob/9dcfcaa0dc8747a2577d3270b69bb9d8313b2807/src/system/packages/brew/cask.rs#L5988-L6024","documentation":"For absolute app targets, mise only allows paths under the effective app dir (default /Applications, overridable via MISE_BREW_CASK_OPT_APPDIR) or under <HOMEBREW_PREFIX>/Applications. When a custom appdir is configured, hardcoded /Applications/... targets are relocated into it via strip_prefix; this bail is what remains for absolute targets that point somewhere else entirely. It is the outer boundary of the app-target containment logic.","triggerScenarios":"A cask declaring an absolute target like \"/Users/Shared/Foo.app\" or \"/Library/Application Support/Foo.app\" that is under neither the app dir nor <prefix>/Applications; or a custom appdir where the target is absolute but not /Applications-anchored, so the relocation branch does not apply.","commonSituations":"Casks that install into non-standard locations; users setting MISE_BREW_CASK_OPT_APPDIR and hitting casks with targets outside /Applications; system-extended casks from third-party taps.","solutions":["If you set MISE_BREW_CASK_OPT_APPDIR, unset it (or point it at the dir the cask actually targets) so the default /Applications containment applies","Edit the cask's target to a bare bundle name (\"Foo.app\") or an absolute /Applications/... path","Install that cask with `brew install --cask <token>` directly — Homebrew may permit target locations mise deliberately refuses","Update mise in case newer builds widened the allowed roots"],"exampleFix":"# before\nexport MISE_BREW_CASK_OPT_APPDIR=/opt/MyApps\n# cask target: \"/Users/Shared/Foo.app\" -> rejected\n# after: unset the override so /Applications containment applies\nunset MISE_BREW_CASK_OPT_APPDIR","handlingStrategy":"validation","validationCode":"use std::path::Path;\nfn app_target_allowed(name: &str, app_dir: &Path, prefix_apps: &Path) -> bool {\n    if !name.contains('/') {\n        return true;\n    }\n    let expanded = name.replace(\"$HOMEBREW_PREFIX\", \"/opt/homebrew\"); // your prefix\n    let p = Path::new(&expanded);\n    p.is_absolute() && (p.starts_with(app_dir) || p.starts_with(prefix_apps) || p.starts_with(\"/Applications\"))\n}","typeGuard":null,"tryCatchPattern":"match app_target_path(name) {\n    Ok(p) => p,\n    Err(e) if e.to_string().contains(\"must be under\") => {\n        warn!(\"cask wants target outside app dir: {name}\");\n        continue;\n    }\n    Err(e) => Err(e),\n}","preventionTips":["Keep MISE_BREW_CASK_OPT_APPDIR unset unless you specifically need relocated /Applications targets","Prefer bare bundle-name targets in custom casks so the app dir is chosen by mise","Remember hardcoded absolute targets outside /Applications and <prefix>/Applications are rejected by design"],"tags":["brew-cask","path-containment","appdir","homebrew"],"backgroundTag":"path-containment-violation","analyzedSha":"9dcfcaa0dc8747a2577d3270b69bb9d8313b2807","analyzedAt":"2026-08-17T14:28:50.624Z","schemaVersion":2},"datasetVersion":"2026-08-22T04:17:13.399Z"}