jdx/mise · error

brew-cask: generic artifact source is not contained by the…

Error message

brew-cask: generic artifact source is not contained by the extraction root: {}

What it means

During generic-artifact cask installation, mise locates the artifact inside the extraction stage and computes its path relative to the stage root. If the resolved source path cannot be expressed as a path strictly contained by the extraction root (including via resolved symlinks), installation aborts. This is a path-containment safety check that prevents a cask-defined `source` glob from matching files outside the extracted payload.

Solutions

  1. Update or fix the cask definition so `source` matches only paths inside the extracted artifact
  2. Re-download the cask payload to rule out a corrupted or altered stage
  3. Check for symlinked ancestors of the mise temp/stage directories and use a real path if a local workaround is needed
  4. Report the cask to the mise/homebrew maintainers if a stock cask triggers this

Example fix

// cask generic artifact before (source escapes the stage)
// source: "/Applications/../.." 
// after
// source: "App.app" (relative to extraction root)
Defensive patterns

Strategy: validation

Validate before calling

let stage = fs::canonicalize(stage_dir)?;
let source = fs::canonicalize(stage_dir.join(cask_source))?;
if !source.starts_with(&stage) {
    return Err(format!("artifact source {source:?} escapes stage {stage:?}"));
}

Type guard

fn is_contained(child: &Path, root: &Path) -> bool {
    std::fs::canonicalize(child).map(|c| c.starts_with(root)).unwrap_or(false)
}

Prevention

When it happens

Trigger: Installing a cask with a `generic` artifact whose `source` resolves (after `find_artifact_matching` traverses symlinks) to a location outside the extraction stage; also triggered when the stage directory itself is behind a symlinked ancestor so lexical and resolved prefixes diverge and `staged_relative_path` fails against both.

Common situations: A cask stanza lists a source path that the package actually places outside the stage (mis-authored or changed upstream cask); a user's temp directory is a symlink (/tmp -> /private/tmp) interacting with stage path resolution; tampered or modified cask definitions.

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/54b28c6179b62adf. Report an issue: GitHub.

Appendix: source

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

            "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) {
        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)?;
    copy_generic_artifact(&source, &target, elevated_target.as_deref())?;
    if let Some(parent) = caskroom_source.parent() {
        file::create_dir_all(parent)?;
    }
    file::make_symlink(&target, &caskroom_source)?;
    targets.record_installed(target);
    Ok(())
}

View on GitHub (pinned to 533346cc37)