jdx/mise · error

brew-cask: refusing generic artifact source outside the…

Error message

brew-cask: refusing generic artifact source outside the extraction root: {}

What it means

For generic (non-app) artifacts, the staged source located by find_artifact_matching must resolve inside the extraction root (path_starts_with_resolved_root uses real paths, not lexical comparison). If the matched source resolves outside the stage — typically via symlinks in the archive pointing elsewhere — the manager refuses to install it, preventing files outside the sandbox from being linked into system locations.

Solutions

  1. Obtain the artifact from the trusted upstream tap and verify its sha256 before installing.
  2. Rebuild/repackage the archive so all contents (including symlinks) live inside the extraction root with relative targets.
  3. Adjust the artifact source pattern so it matches a real file inside the archive rather than an escaping symlink.
  4. If intentional, restructure the cask to use preflight/postflight scripts instead of linking outside-root content.

Example fix

// before (archive)
bin/tool -> /usr/local/bin/tool
// after
bin/tool -> ../libexec/tool  # relative link inside archive
Defensive patterns

Strategy: validation

Validate before calling

use std::path::Path;
fn source_inside_root(root: &Path, source: &Path) -> bool {
    source.canonicalize()
        .ok()
        .and_then(|r| r.strip_prefix(root.canonicalize().ok()?).ok())
        .is_some()
}

Try / catch

match install_result {
    Err(e) if e.to_string().contains("outside the extraction root") => {
        eprintln!("generic artifact source resolves outside the archive; repackage or use a trusted artifact");
    }
    other => other?,
}

Prevention

When it happens

Trigger: Installing a cask with a GenericArtifact whose resolved source (after symlink resolution by the artifact lookup) is not under the extraction root — e.g. the source pattern matched a symlink targeting a path outside the stage.

Common situations: Archives shipping symlinks to absolute paths or '../..' targets; malicious/tampered cask artifacts; generic artifacts whose glob accidentally matches an extractor-created escape symlink.

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

Appendix: source

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

        let Ok(relative) = source.strip_prefix(temporary_caskroom) else {
            continue;
        };
        file::remove_file(&target)?;
        create_flight_symlink(&final_caskroom.join(relative), &target, FlightSudo::Never)?;
    }
    Ok(())
}

fn install_generic_artifact(
    stage: &Path,
    temporary_caskroom: &Path,
    artifact: &GenericArtifact,
    targets: &mut FlightTargetTransaction,
) -> Result<()> {
    let source = find_artifact_matching(stage, &artifact.source, |_| true)
        .ok_or_else(|| eyre!("brew-cask: artifact '{}' was not found", artifact.source))?;
    if !path_starts_with_resolved_root(&source, stage) {
        bail!(
            "brew-cask: refusing generic artifact source outside the extraction root: {}",
            source.display()
        );
    }
    let target = generic_artifact_target_path(&artifact.target)?;
    // Not a lexical `strip_prefix`: the lookup resolves symlinks it had to
    // traverse, so a source reached that way can be contained by the stage
    // without sharing its literal prefix — as it is whenever `stage` itself
    // has a symlinked ancestor. `staged_relative_path` retries against the
    // resolved stage, matching the containment check above.
    let relative_source = staged_relative_path(stage, &source).ok_or_else(|| {
        eyre!(
            "brew-cask: generic artifact source is not contained by the extraction root: {}",
            source.display()
        )
    })?;
    let caskroom_source = temporary_caskroom.join(relative_source);
    if !path_starts_with_resolved_root(&caskroom_source, temporary_caskroom) {

View on GitHub (pinned to 533346cc37)