{"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/533346cc374382b41ec5ff70536252b2e96e725c/src/system/packages/brew/cask/paths.rs#L159-L195","documentation":"The MISE_BREW_CASK_OPT_APPDIR override must not contain a '..' component. Because the appdir acts as a containment boundary for privileged cask mutations, a parent-directory component could let the appdir resolve outside its intended location; target_app_dir rejects any value with '..' outright rather than normalizing it (src/system/packages/brew/cask/paths.rs:177).","triggerScenarios":"Setting MISE_BREW_CASK_OPT_APPDIR to a value containing '..' — e.g. '/opt/apps/../Applications' or '/Applications/../../tmp' — and invoking any cask install/validation path that reaches target_app_dir (via target_path, cask_appdir, app_target_path, or allowed_appdir_roots).","commonSituations":"Users trying to point the appdir at a sibling directory using '..' shorthand; templated config where variable interpolation leaves a '..' segment; attempts to loosen or redirect the installation boundary that the validation deliberately blocks.","solutions":["Rewrite the path without '..': resolve it yourself to the real absolute directory, e.g. /opt/apps/../Applications -> /opt/Applications.","Symlink the desired location to a clean absolute path and set MISE_BREW_CASK_OPT_APPDIR to that path.","If the '..' came from templating or interpolation, fix the template so it emits a fully resolved absolute path.","Keep the appdir inside an intended subtree; the validator intentionally forbids escape attempts, so choose a direct absolute path."],"exampleFix":"// before\nexport MISE_BREW_CASK_OPT_APPDIR=/opt/apps/../Applications\n// after\nexport MISE_BREW_CASK_OPT_APPDIR=/opt/Applications","handlingStrategy":"validation","validationCode":"function isValidAppdir(v) {\n  const p = v ? path.resolve(v) : null;\n  return !!p && path.isAbsolute(v) && !v.split('/').includes('..') && p !== '/';\n}","typeGuard":"function isSafeAppdir(v) {\n  return typeof v === \"string\" && v.length > 0 && path.isAbsolute(v) &&\n    !v.includes('..') && path.resolve(v) !== path.parse(v).root;\n}","tryCatchPattern":null,"preventionTips":["Always export MISE_BREW_CASK_OPT_APPDIR as a clean absolute path with no '..' segments.","Run realpath on the intended directory (and check it is not '/') before exporting it.","Prefer creating a dedicated directory (mkdir -p) over reusing paths that need '..' to reach.","Fix templating so interpolated values emit fully resolved absolute paths."],"tags":["brew-cask","env-var","path-traversal"],"backgroundTag":"path-traversal-blocked","analyzedSha":"533346cc374382b41ec5ff70536252b2e96e725c","analyzedAt":"2026-09-17T13:35:38.149Z","contentChangedAt":"2026-09-17T13:35:38.149Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}