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
- Obtain the artifact from the trusted upstream tap and verify its sha256 before installing.
- Rebuild/repackage the archive so all contents (including symlinks) live inside the extraction root with relative targets.
- Adjust the artifact source pattern so it matches a real file inside the archive rather than an escaping symlink.
- 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
- Check resolved (canonicalized) paths, not just lexical, before linking artifacts.
- Reject archives containing symlinks that point outside the stage.
- Verify cask artifact checksums from the tap before installation.
- Keep generic artifact source globs narrow so they match real files, not escape symlinks.
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
- brew-cask: staged symlink path escaped extraction root
- brew-cask: invalid app source
- brew-cask: invalid structured flight path
- brew-cask: refusing to stage cask payload through a path…
- expected symlinked appdir tail to be rejected
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)