jdx/mise · error

brew-cask: {APP_DIR_ENV} '{}' must not contain '..'

Error message

brew-cask: {APP_DIR_ENV} '{}' must not contain '..'

What it means

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.

Source

Thrown at src/system/packages/brew/cask/paths.rs:177

pub(super) fn target_app_dir() -> Result<PathBuf> {
    let Ok(dir) = crate::env::var(APP_DIR_ENV) else {
        return Ok(PathBuf::from(DEFAULT_APP_DIR));
    };
    if dir.is_empty() {
        return Ok(PathBuf::from(DEFAULT_APP_DIR));
    }
    let dir = PathBuf::from(dir);
    if !dir.is_absolute() {
        bail!(
            "brew-cask: {APP_DIR_ENV} '{}' must be an absolute path",
            dir.display()
        );
    }
    if dir
        .components()
        .any(|component| matches!(component, Component::ParentDir))
    {
        bail!(
            "brew-cask: {APP_DIR_ENV} '{}' must not contain '..'",
            dir.display()
        );
    }
    // Resolve the override to a real absolute path: canonicalize its longest
    // existing prefix and re-append the components that do not exist yet. This
    // makes the appdir a symlink-free containment boundary — privileged cask
    // mutations then operate on resolved paths and cannot be redirected through
    // a symlinked component — and it collapses every spelling of the filesystem
    // root (`/`, `//`, `/.`, a symlink to `/`, ...) to `/` so they can all be
    // rejected together.
    let resolved = resolve_appdir(&dir);
    if !resolved
        .components()
        .any(|component| matches!(component, Component::Normal(_)))
    {
        bail!(
            "brew-cask: {APP_DIR_ENV} '{}' must not resolve to the filesystem root",

View on GitHub (pinned to afd2eddd3a)

Solutions

  1. Rewrite the env var value without `..`, using the real absolute path (e.g. `/Users/other/Apps` instead of `/Users/me/../other/Apps`)
  2. Canonicalize the value when setting it: `export MISE_BREW_CASK_OPT_APPDIR="$(cd /path/../real && pwd)"` in a context where it resolves as intended
  3. Fix the build/script logic that composes the path so it never emits `..`
  4. Unset the var to use the default app dir

Example fix

// before (shell)
export MISE_BREW_CASK_OPT_APPDIR=/Users/me/../shared/Apps
// after
export MISE_BREW_CASK_OPT_APPDIR=/Users/shared/Apps
Defensive patterns

Strategy: validation

Validate before calling

fn appdir_env_ok(v: &str) -> bool {
    let p = std::path::Path::new(v);
    p.is_absolute() && !p.components().any(|c| matches!(c, std::path::Component::ParentDir))
}

Type guard

fn has_no_parent_dirs(p: &std::path::Path) -> bool {
    !p.components().any(|c| matches!(c, std::path::Component::ParentDir))
}

Try / catch

match result {
    Err(e) if e.to_string().contains("must not contain '..'") && e.to_string().contains("APPDIR") => {
        eprintln!("rewrite the override without '..' components");
    }
    r => r?,
}

Prevention

When it happens

Trigger: 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`).

Common situations: 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.

Understand the failure class

Background: Path traversal blocked: "path escapes the workspace" and "outside site root" errors when a path will not stay inside its allowed directory — this error's family across 26 libraries.

Related errors


AI-assisted analysis of jdx/mise@afd2eddd3a (2026-09-09). Data as JSON: /api/errors/b0de1d2ca0e0ff85. Report an issue: GitHub.