jdx/mise · critical
brew-cask: staged symlink path escaped extraction root
Error message
brew-cask: staged symlink path escaped extraction root: {} What it means
While copying the staged tree into mise's private caskroom (src/system/packages/brew/cask.rs:1688), every path must resolve inside the extraction root. If staged_relative_path cannot express a source relative to the stage, or the source's parent is not under the resolved stage root, the payload contains a symlink that escapes the extraction directory, and mise refuses to follow it.
Solutions
- Inspect the cask's payload yourself: download the cask URL, extract it, and run `find . -type l -ls` to see the escaping symlink
- Treat this as a security boundary, not a bug — do not install casks from taps you do not trust
- Prefer the official homebrew/cask build of the app
- Report the offending cask to the tap maintainer
Defensive patterns
Strategy: validation
Validate before calling
# reject escaping symlinks before install
find "$EXTRACT_DIR" -type l -printf '%p -> %l' | grep -E '\.\.|^/' && { echo 'escaping symlink'; exit 1; } Prevention
- Only install casks from taps you trust — the escape check is a security boundary
- Scan extracted archives in CI for absolute or ../ symlinks before republishing them as casks
- Treat any hit of this error as a supply-chain incident, not a bug to work around
When it happens
Trigger: An extracted cask archive containing absolute symlinks or links traversing '../../..' out of the extraction directory, so an installed artifact would point outside the staged tree — the classic hostile-payload shape this guard exists for.
Common situations: Malicious or repackaged third-party casks; archives built with unusual tooling that symlink to /tmp or system locations; taps whose artifacts were re-packed after tampering.
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: refusing generic artifact source outside the…
- brew-cask: app target
- brew-cask: binary target
- brew-cask: completion target
- brew-cask: completion target
AI-assisted analysis of jdx/mise@6f52dcdf99 (2026-08-22).
Data as JSON: /api/errors/76c67b62f58702ae.
Report an issue: GitHub.
Appendix: source
Thrown at src/system/packages/brew/cask.rs:1688
}
fn copy_staged_artifact_closure(stage: &Path, owned_stage: &Path, source: &Path) -> Result<()> {
let stage = lexically_normalized_path(stage);
let mut pending = vec![lexically_normalized_path(source)];
let mut visited = BTreeSet::new();
while let Some(source) = pending.pop() {
let relative = staged_relative_path(&stage, &source).ok_or_else(|| {
eyre!(
"brew-cask: staged symlink target escaped extraction root: {}",
source.display()
)
})?;
if relative.components().next().is_some()
&& !source
.parent()
.is_some_and(|parent| path_starts_with_resolved_root(parent, &stage))
{
bail!(
"brew-cask: staged symlink path escaped extraction root: {}",
source.display()
);
}
if !visited.insert(relative.to_path_buf()) {
continue;
}
let destination = owned_stage.join(&relative);
let metadata = source.symlink_metadata()?;
if destination.symlink_metadata().is_err() {
if let Some(parent) = destination.parent() {
file::create_dir_all(parent)?;
}
if metadata.file_type().is_symlink() {
file::make_symlink(&std::fs::read_link(&source)?, &destination)?;
} else {
copy_cask_artifact(&source, &destination)?;
}View on GitHub (pinned to 6f52dcdf99)