jdx/mise · error

brew-cask: refusing generic artifact source outside the extr

Error message

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

What it means

When installing a cask's generic artifact, mise locates the source file matching artifact.source inside the extraction (stage) root, then verifies with path_starts_with_resolved_root that the found path is actually resolved-contained within the stage. If the resolved location falls outside the extraction root (e.g. the lookup had to traverse a symlink pointing out of the tree), installation is refused to keep a cask from exfiltrating or overwriting files outside its own payload.

Source

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

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

Solutions

  1. Inspect the extracted stage for out-of-tree symlinks and remove/fix them before reinstalling
  2. Re-download/re-extract the cask to rule out corruption or tampering
  3. Ensure mise's caskroom/stage paths are not themselves behind unexpected symlinks
  4. Report the cask upstream if the artifact genuinely lives outside the payload
Defensive patterns

Strategy: validation

Validate before calling

// verify the artifact source stays inside the stage after resolution
let resolved = dunce::canonicalize(stage.join(&artifact.source))?;
if !resolved.starts_with(dunce::canonicalize(stage)?) {
    return Err(format!("artifact source resolves outside stage: {}", resolved.display()));
}

Prevention

When it happens

Trigger: Calling install_generic_artifact where find_artifact_matching resolves the cask's declared `source` glob/pattern to a path whose fully-resolved location is not under the stage directory. Typically caused by symlinked directories inside the extracted payload pointing out of the tree.

Common situations: A maliciously or accidentally crafted cask whose payload contains symlinks out of the stage; the artifact source pattern matching a symlinked path that resolves elsewhere; a stage directory placed under a symlinked path interacting with lookup resolution.

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@afd2eddd3a (2026-09-09). Data as JSON: /api/errors/82025f9ffec2a3f8. Report an issue: GitHub.