{"record":{"id":"75f3d1f0f416bfea","repo":"jdx/mise","slug":"brew-cask-invalid-appdir-75f3d1","errorCode":null,"errorMessage":"brew-cask: invalid appdir '{}'","messagePattern":"brew-cask: invalid appdir '(.+?)'","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"src/system/packages/brew/cask/paths.rs","lineNumber":276,"sourceCode":"}\n\npub(super) fn allowed_binary_target_roots_display(roots: &[PathBuf]) -> String {\n    roots\n        .iter()\n        .map(|root| root.display().to_string())\n        .collect::<Vec<_>>()\n        .join(\" or \")\n}\n\npub(super) fn binary_target_path(target_name: &str, appdir: &Path) -> Result<PathBuf> {\n    if target_name.contains('\\0') {\n        bail!(\"brew-cask: binary target contains NUL\");\n    }\n    if let Some(relative) = target_name.strip_prefix(\"$APPDIR/\") {\n        let relative = Path::new(relative);\n        reject_appdir_escape(relative, \"binary $APPDIR target\", target_name)?;\n        if !allowed_appdir_roots()?.iter().any(|root| root == appdir) {\n            bail!(\"brew-cask: invalid appdir '{}'\", appdir.display());\n        }\n        return Ok(appdir.join(relative));\n    }\n    if target_name.contains(\"$APPDIR\") {\n        bail!(\"brew-cask: $APPDIR must prefix a binary target\");\n    }\n    let prefix = prefix::prefix();\n    let prefix_str = prefix.to_string_lossy();\n    let target_name = target_name.replace(\"$HOMEBREW_PREFIX\", prefix_str.as_ref());\n    let path = PathBuf::from(&target_name);\n    let target = if path.is_absolute() {\n        path\n    } else if target_name.contains('/') {\n        prefix.join(path)\n    } else {\n        prefix.join(\"bin\").join(path)\n    };\n    if target","sourceCodeStart":258,"sourceCodeEnd":294,"githubUrl":"https://github.com/jdx/mise/blob/afd2eddd3a50c16190efc1c7e94404b48f72af57/src/system/packages/brew/cask/paths.rs#L258-L294","documentation":"A cask `binary` target written as `$APPDIR/<rel>` is joined to the appdir in use; that appdir must be one of the allowed app-dir roots (`/Applications` by default, the APP_DIR override, or the Homebrew prefix's Applications dir). `binary_target_path` rejects a `$APPDIR/...` target when the supplied appdir is not in that allowlist, preventing binaries from being linked into arbitrary directories via the appdir parameter.","triggerScenarios":"Calling `binary_target_path(target, appdir)` (via `target_path` or `binary_targets_must_stay_under_an_allowed_root`) with a target starting `$APPDIR/` and an `appdir` argument not equal to any entry of `allowed_appdir_roots()` — e.g. a caller passing a staging path or a custom directory as appdir.","commonSituations":"Custom cask tooling passing its own directory as appdir; an APP_DIR override typo making the computed appdir mismatch the allowed roots; stale code assuming any directory works with `$APPDIR` targets.","solutions":["Pass the appdir obtained from mise's own resolution (the `cask_appdir`/`target_app_dir` value) rather than an arbitrary directory","Ensure the APP_DIR env override, if set, is a valid absolute path so it lands in `allowed_appdir_roots()`","Rewrite the cask target to a prefix-anchored path (`$HOMEBREW_PREFIX/bin/...`) if it is not meant to live in the appdir","Update the calling code so `$APPDIR` targets are only evaluated against allowed appdir roots"],"exampleFix":"// before (Rust caller)\nbinary_target_path(\"$APPDIR/bin/tool\", &staging_dir)?\n// after\nlet appdir = cask_appdir()?;\nbinary_target_path(\"$APPDIR/bin/tool\", &appdir)?","handlingStrategy":"validation","validationCode":"fn appdir_allowed(appdir: &std::path::Path) -> bool {\n    appdir == std::path::Path::new(\"/Applications\")\n        || appdir == std::path::Path::new(\"/opt/homebrew/Applications\")\n        || appdir == std::path::Path::new(\"/usr/local/Applications\")\n}","typeGuard":null,"tryCatchPattern":"match result {\n    Err(e) if e.to_string().contains(\"invalid appdir\") => {\n        eprintln!(\"pass an allowed appdir root for $APPDIR binary targets\");\n    }\n    r => r?,\n}","preventionTips":["Only evaluate $APPDIR/... targets against the appdir returned by mise's own resolution","Do not pass staging or arbitrary directories as the appdir","Keep any APP_DIR override valid and absolute so it joins the allowed roots","Prefer $HOMEBREW_PREFIX targets when a binary is not meant for the appdir"],"tags":["path-validation","brew-cask","allowlist"],"backgroundTag":"invalid-argument-value","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"}