{"record":{"id":"7388ca343a900bc3","repo":"jdx/mise","slug":"brew-cask-app-dir-env-must-not-resolve-to-7388ca","errorCode":null,"errorMessage":"brew-cask: {APP_DIR_ENV} '{}' must not resolve to the filesystem root","messagePattern":"brew-cask: (.+?) '(.+?)' must not resolve to the filesystem root","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"src/system/packages/brew/cask/paths.rs","lineNumber":194,"sourceCode":"    {\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\",\n            dir.display()\n        );\n    }\n    Ok(resolved)\n}\n\n/// Resolve `dir` by canonicalizing its longest existing ancestor and\n/// re-appending the not-yet-existing tail. Symlinks in the existing portion are\n/// followed, so the result is a real path the caller can safely use as a\n/// containment boundary. Falls back to `dir` unchanged if nothing along the\n/// path can be canonicalized (not expected for an absolute path, where `/`\n/// always resolves).\npub(super) fn resolve_appdir(dir: &Path) -> PathBuf {\n    for ancestor in dir.ancestors() {\n        if let Ok(real) = ancestor.canonicalize() {\n            let tail = dir.strip_prefix(ancestor).unwrap_or(Path::new(\"\"));\n            return real.join(tail);","sourceCodeStart":176,"sourceCodeEnd":212,"githubUrl":"https://github.com/jdx/mise/blob/afd2eddd3a50c16190efc1c7e94404b48f72af57/src/system/packages/brew/cask/paths.rs#L176-L212","documentation":"The brew-cask app-dir override must resolve to a real directory, not the filesystem root `/`. Because the resolved appdir is the containment boundary for privileged cask mutations, allowing `/` would let app targets be placed anywhere on disk. After resolving symlinks and normalizing (`/`, `//`, `/.`, symlink-to-`/`), `target_app_dir` rejects any value with no normal components.","triggerScenarios":"Setting APP_DIR_ENV to `/`, `//`, `/.`, a path consisting only of `.`/symlinks that resolve to `/` (e.g. a symlink named /rootlink pointing at `/`, value `/rootlink`), then invoking any code path that resolves the app dir.","commonSituations":"Placeholder value never properly configured; a script defaulting to `/` when a variable is empty; a symlink experiment pointing at the root; attempting to 'allow everything' by pointing the appdir at `/`.","solutions":["Set the env var to a real subdirectory, e.g. `export MISE_BREW_CASK_OPT_APPDIR=/Users/me/Applications`","Unset the var so mise uses the default `/Applications`","Remove or repoint any symlink intended as the appdir that resolves to `/`","Fix the script that substitutes an empty/default value with `/`"],"exampleFix":"// before (shell)\nexport MISE_BREW_CASK_OPT_APPDIR=/\n// after\nexport MISE_BREW_CASK_OPT_APPDIR=\"$HOME/Applications\"","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::Normal(_)))\n}","typeGuard":null,"tryCatchPattern":"match result {\n    Err(e) if e.to_string().contains(\"must not resolve to the filesystem root\") => {\n        eprintln!(\"point the override at a real subdirectory, not '/'\");\n    }\n    r => r?,\n}","preventionTips":["Never use '/' or a symlink-to-'/' as the override","Fix scripts that default empty variables to '/'","Point the override at a dedicated applications directory","If you want everything allowed, this is not the mechanism — use an appropriate subdir"],"tags":["env-var","path-validation","brew-cask","security"],"backgroundTag":"invalid-env-var-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"}