jdx/mise · error · eyre::Report

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

Error message

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

What it means

After confirming MISE_BREW_CASK_OPT_APPDIR is absolute, target_app_dir rejects any value whose components contain '..'. Because the app dir is used as a symlink-free containment boundary for privileged writes, lexical parent-directory components would let a cask escape it. This mirrors the '..' checks applied to individual cask targets.

Source

Thrown at src/system/packages/brew/cask.rs:6042

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 9dcfcaa0dc)

Solutions

  1. Export the normalized path without '..': `export MISE_BREW_CASK_OPT_APPDIR=/opt/MyApps`
  2. Generate the value with realpath: `export MISE_BREW_CASK_OPT_APPDIR="$(realpath ~/Applications)"`
  3. Or unset the variable to fall back to /Applications

Example fix

# before
export MISE_BREW_CASK_OPT_APPDIR=/Applications/../opt/MyApps
# after
export MISE_BREW_CASK_OPT_APPDIR=/opt/MyApps
Defensive patterns

Strategy: validation

Validate before calling

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

Try / catch

match target_app_dir() {
    Ok(dir) => dir,
    Err(e) if e.to_string().contains("must not contain '..'") => {
        let clean = std::path::PathBuf::from(val).canonicalize()?;
        std::env::set_var("MISE_BREW_CASK_OPT_APPDIR", clean);
        target_app_dir()
    }
    Err(e) => Err(e),
}

Prevention

When it happens

Trigger: Exporting MISE_BREW_CASK_OPT_APPDIR="/Applications/../opt/MyApps" or any absolute path containing a '..' component.

Common situations: Users shortening paths with '..' in dotfiles or CI env; scripts concatenating path fragments that produce '..' segments.

Related errors


AI-assisted analysis of jdx/mise@9dcfcaa0dc (2026-08-17). Data as JSON: /api/errors/ec6713fb5c55a3e3. Report an issue: GitHub.