jdx/mise · error

brew-cask: ' ' must not contain '..

Error message

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

What it means

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).

Solutions

  1. Rewrite the path without '..': resolve it yourself to the real absolute directory, e.g. /opt/apps/../Applications -> /opt/Applications.
  2. Symlink the desired location to a clean absolute path and set MISE_BREW_CASK_OPT_APPDIR to that path.
  3. If the '..' came from templating or interpolation, fix the template so it emits a fully resolved absolute path.
  4. Keep the appdir inside an intended subtree; the validator intentionally forbids escape attempts, so choose a direct absolute path.

Example fix

// before
export MISE_BREW_CASK_OPT_APPDIR=/opt/apps/../Applications
// after
export MISE_BREW_CASK_OPT_APPDIR=/opt/Applications
Defensive patterns

Strategy: validation

Validate before calling

function isValidAppdir(v) {
  const p = v ? path.resolve(v) : null;
  return !!p && path.isAbsolute(v) && !v.split('/').includes('..') && p !== '/';
}

Type guard

function isSafeAppdir(v) {
  return typeof v === "string" && v.length > 0 && path.isAbsolute(v) &&
    !v.includes('..') && path.resolve(v) !== path.parse(v).root;
}

Prevention

When it happens

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

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

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@533346cc37 (2026-09-17). Data as JSON: /api/errors/b0de1d2ca0e0ff85. Report an issue: GitHub.

Appendix: 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 533346cc37)