{"record":{"id":"b0de1d2ca0e0ff85","repo":"jdx/mise","slug":"brew-cask-app-dir-env-must-not-contain-b0de1d","errorCode":null,"errorMessage":"brew-cask: {APP_DIR_ENV} '{}' must not contain '..'","messagePattern":"brew-cask: (.+?) '(.+?)' must not contain '\\.\\.'","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"src/system/packages/brew/cask/paths.rs","lineNumber":177,"sourceCode":"pub(super) fn target_app_dir() -> Result<PathBuf> {\n    let Ok(dir) = crate::env::var(APP_DIR_ENV) else {\n        return Ok(PathBuf::from(DEFAULT_APP_DIR));\n    };\n    if dir.is_empty() {\n        return Ok(PathBuf::from(DEFAULT_APP_DIR));\n    }\n    let dir = PathBuf::from(dir);\n    if !dir.is_absolute() {\n        bail!(\n            \"brew-cask: {APP_DIR_ENV} '{}' must be an absolute path\",\n            dir.display()\n        );\n    }\n    if dir\n        .components()\n        .any(|component| matches!(component, Component::ParentDir))\n    {\n        bail!(\n            \"brew-cask: {APP_DIR_ENV} '{}' must not contain '..'\",\n            dir.display()\n        );\n    }\n    // Resolve the override to a real absolute path: canonicalize its longest\n    // existing prefix and re-append the components that do not exist yet. This\n    // makes the appdir a symlink-free containment boundary — privileged cask\n    // mutations then operate on resolved paths and cannot be redirected through\n    // a symlinked component — and it collapses every spelling of the filesystem\n    // root (`/`, `//`, `/.`, a symlink to `/`, ...) to `/` so they can all be\n    // rejected together.\n    let resolved = resolve_appdir(&dir);\n    if !resolved\n        .components()\n        .any(|component| matches!(component, Component::Normal(_)))\n    {\n        bail!(\n            \"brew-cask: {APP_DIR_ENV} '{}' must not resolve to the filesystem root\",","sourceCodeStart":159,"sourceCodeEnd":195,"githubUrl":"https://github.com/jdx/mise/blob/afd2eddd3a50c16190efc1c7e94404b48f72af57/src/system/packages/brew/cask/paths.rs#L159-L195","documentation":"The brew-cask app-dir override env var must not contain `..` components; `target_app_dir` uses the value as a symlink-free containment boundary for privileged operations, and a `..` component could redirect installs outside the intended directory. The value is rejected before any resolution occurs.","triggerScenarios":"Exporting APP_DIR_ENV to a path containing `..` (e.g. `MISE_BREW_CASK_OPT_APPDIR=/Users/me/../etc/Apps`) and then running cask install/validation that calls `target_app_dir` (directly or through `app_target_path`, `cask_appdir`, `target_path`, `allowed_appdir_roots`).","commonSituations":"Path assembled by string concatenation from variables that produce `..`; users trying to normalize a path by hand; scripts that append `../..` to 'go up' a tree.","solutions":["Rewrite the env var value without `..`, using the real absolute path (e.g. `/Users/other/Apps` instead of `/Users/me/../other/Apps`)","Canonicalize the value when setting it: `export MISE_BREW_CASK_OPT_APPDIR=\"$(cd /path/../real && pwd)\"` in a context where it resolves as intended","Fix the build/script logic that composes the path so it never emits `..`","Unset the var to use the default app dir"],"exampleFix":"// before (shell)\nexport MISE_BREW_CASK_OPT_APPDIR=/Users/me/../shared/Apps\n// after\nexport MISE_BREW_CASK_OPT_APPDIR=/Users/shared/Apps","handlingStrategy":"validation","validationCode":"fn appdir_env_ok(v: &str) -> bool {\n    let p = std::path::Path::new(v);\n    p.is_absolute() && !p.components().any(|c| matches!(c, std::path::Component::ParentDir))\n}","typeGuard":"fn has_no_parent_dirs(p: &std::path::Path) -> bool {\n    !p.components().any(|c| matches!(c, std::path::Component::ParentDir))\n}","tryCatchPattern":"match result {\n    Err(e) if e.to_string().contains(\"must not contain '..'\") && e.to_string().contains(\"APPDIR\") => {\n        eprintln!(\"rewrite the override without '..' components\");\n    }\n    r => r?,\n}","preventionTips":["Compose the override from literal absolute segments, never by appending '..'","Canonicalize paths in scripts before exporting them","Treat '..' in config values as a smell — replace with the resolved path","Unset the var rather than hacking around a bad value"],"tags":["env-var","path-traversal","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"}