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

A cask's generic `artifact` stanza says copy stage/<source> to <target>. mise locates the source with find_artifact_matching, then requires that after resolving symlinks the source still sits inside the extraction root (path_starts_with_resolved_root). If resolution jumps outside the stage, the copy is refused — a symlinked source could otherwise exfiltrate arbitrary files into install targets.

Source

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

        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)?;
    let relative_source = source.strip_prefix(stage)?;
    let caskroom_source = temporary_caskroom.join(relative_source);
    if !path_starts_with_resolved_root(&caskroom_source, temporary_caskroom) {
        bail!(
            "brew-cask: refusing to stage generic artifact through a path outside the caskroom: {}",
            caskroom_source.display()
        );
    }
    #[cfg(not(unix))]
    if let Some(parent) = target.parent() {
        file::create_dir_all(parent)?;
    }
    let elevated_target = targets.protect_generic(&target)?;

View on GitHub (pinned to 9dcfcaa0dc)

Solutions

  1. Inspect the cask's archive and artifact stanza; verify no matched source entry is an escaping symlink
  2. If you maintain the tap, point artifact.source at real files inside the archive
  3. Otherwise report the cask — the guard is preventing an out-of-root copy
Defensive patterns

Strategy: try-catch

Validate before calling

// Tap authors: before publishing, confirm every artifact source is a real file inside the archive
let real = stage.join(&artifact.source);
if real.symlink_metadata().map_or(true, |m| !m.is_symlink()) == false {
    panic!("artifact source {} is a symlink", artifact.source);
}

Try / catch

Catch 'refusing generic artifact source outside the extraction root' and abort the cask install; inspect the archive and report — the guard exists to stop out-of-root copies, so no remediation in-process is appropriate.

Prevention

When it happens

Trigger: Cask JSON whose artifact.source matches a symlink in the archive that resolves to a path outside the staged extraction (absolute link or ../../ escape), or a glob that matched such a link.

Common situations: Malicious taps; archives whose entries are symlinks into system paths; tap authoring mistakes where source patterns unintentionally match link entries.

Related errors


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