jdx/mise · error · eyre::Report

brew-cask: binary target '{}' must not contain '..'

Error message

brew-cask: binary target '{}' must not contain '..'

What it means

binary_target_path expands $HOMEBREW_PREFIX, anchors relative targets under the brew prefix (or prefix/bin for bare names), then rejects any result containing a Component::ParentDir ('..'). This prevents a binary target from escaping the allowed install roots after expansion and anchoring.

Source

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

    if target_name.contains("$APPDIR") {
        bail!("brew-cask: $APPDIR must prefix a binary target");
    }
    let prefix = prefix::prefix();
    let prefix_str = prefix.to_string_lossy();
    let target_name = target_name.replace("$HOMEBREW_PREFIX", prefix_str.as_ref());
    let path = PathBuf::from(&target_name);
    let target = if path.is_absolute() {
        path
    } else if target_name.contains('/') {
        prefix.join(path)
    } else {
        prefix.join("bin").join(path)
    };
    if target
        .components()
        .any(|component| matches!(component, Component::ParentDir))
    {
        bail!(
            "brew-cask: binary target '{}' must not contain '..'",
            target.display()
        );
    }
    let roots = allowed_binary_target_roots();
    if !roots.iter().any(|root| target.starts_with(root)) {
        bail!(
            "brew-cask: binary target '{}' must be under {}",
            target.display(),
            allowed_binary_target_roots_display(&roots)
        );
    }
    Ok(target)
}

fn installed_version(token: &str) -> Option<String> {
    let versions = installed_versions(token);
    match versions.as_slice() {

View on GitHub (pinned to 9dcfcaa0dc)

Solutions

  1. Use a clean target: bare "tool" (goes to <prefix>/bin), "sbin/tool" (goes to <prefix>/sbin), or "$HOMEBREW_PREFIX/..." without '..'
  2. Report/fix the malformed artifact stanza in the tap
  3. Verify with `brew info --json=v2 --cask <token>` what target is actually published

Example fix

// before (cask binary target)
"target": "../sbin/tool"
// after
"target": "sbin/tool"
Defensive patterns

Strategy: type-guard

Type guard

use std::path::{Component, Path};
fn binary_target_has_no_dotdot(name: &str, homebrew_prefix: &str) -> bool {
    let expanded = name.replace("$HOMEBREW_PREFIX", homebrew_prefix);
    !Path::new(&expanded)
        .components()
        .any(|c| matches!(c, Component::ParentDir))
}

Try / catch

match binary_target_path(name, &appdir) {
    Ok(p) => p,
    Err(e) if e.to_string().contains("must not contain '..'") => {
        warn!("skipping binary target with '..': {name}");
        continue;
    }
    Err(e) => Err(e),
}

Prevention

When it happens

Trigger: A binary target like "../sbin/tool", "$HOMEBREW_PREFIX/../usr/bin/tool", or any target that yields a '..' component after expansion/anchoring.

Common situations: Malformed tap casks; hand-edited cask overrides trying to redirect binaries outside the prefix; templated targets that concatenate '..' fragments.

Related errors


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