jdx/mise · error

brew-cask: generic artifact target

Error message

brew-cask: generic artifact target '{}' must stay below {}

What it means

mise's brew-cask backend validates that a generic (non-app, non-font) artifact's install target stays under the mise/brew prefix. The target must not traverse upward with '..' components, must have a component below the prefix, and must resolve inside the prefix root after path resolution. Otherwise installation is refused, preventing cask artifact declarations from writing files into arbitrary system locations.

Solutions

  1. Change the artifact target to a path relative to and below the configured prefix (e.g. 'share/fonts/...' not '/Library/Fonts').
  2. Remove any '..' components from the target path.
  3. Verify your brew prefix configuration; if you use a custom prefix, ensure artifact targets are defined relative to it.
  4. Use a dedicated cask or mise feature (e.g. font artifacts) for locations outside the prefix instead of generic artifacts.
  5. Update the cask to a newer revision where the upstream fixed the target path.

Example fix

// before
artifact target: "/Library/Scripts/mytool"
// after
artifact target: "share/scripts/mytool"
Defensive patterns

Strategy: validation

Validate before calling

let target = std::path::PathBuf::from(declared_target);
if target.components().any(|c| c == std::path::Component::ParentDir)
    || target.is_absolute()
{
    eprintln!("artifact target must be relative and below the prefix");
    std::process::exit(1);
}

Type guard

fn stays_below_prefix(t: &std::path::Path, prefix: &std::path::Path) -> bool {
    !t.components().any(|c| c == std::path::Component::ParentDir)
        && t.strip_prefix(prefix).is_ok()
}

Prevention

When it happens

Trigger: A cask declares a generic artifact whose target path contains '..' components; the target is an absolute path outside the prefix; path_starts_with_resolved_root fails because the target resolves (via symlinks) outside the prefix; a malformed artifact target with no components under the prefix.

Common situations: Hand-written or third-party casks with targets like '/Library/Fonts' or '~/bin' outside the managed prefix; prefix relocations (custom HOMEBREW_PREFIX) making previously valid relative targets resolve outside; users editing cask stanza targets to redirect artifacts.

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/fe0072ba0324e60e. Report an issue: GitHub.

Appendix: source

Thrown at src/system/packages/brew/cask/mod.rs:2440

        run_installer_artifact(stage, installer, targets.copied_files())?;
        completed(index)?;
    }
    durabilize_staged_symlink_targets(stage, temporary_caskroom, targets)
}

fn generic_artifact_target_path(target: &str) -> Result<PathBuf> {
    let prefix = prefix::prefix();
    let expanded = target.replace("$HOMEBREW_PREFIX", &prefix.to_string_lossy());
    let target = PathBuf::from(expanded);
    if !target.is_absolute()
        || !target.starts_with(&prefix)
        || target.strip_prefix(&prefix)?.components().next().is_none()
        || target
            .components()
            .any(|component| matches!(component, Component::ParentDir))
        || !path_starts_with_resolved_root(&target, &prefix)
    {
        bail!(
            "brew-cask: generic artifact target '{}' must stay below {}",
            target.display(),
            prefix.display()
        );
    }
    Ok(target)
}

/// The receipt of the currently installed version, if there is one.
fn previous_receipt(cask: &Cask) -> Result<Option<CaskReceipt>> {
    let Some(version) = installed_version(&cask.token) else {
        return Ok(None);
    };
    read_receipt(&caskroom_version_dir(&cask.token, &version))
}

fn previous_generic_targets(cask: &Cask) -> Result<Vec<CaskTargetRecord>> {
    let Some(receipt) = previous_receipt(cask)? else {

View on GitHub (pinned to 533346cc37)