jdx/mise · error
brew-cask: ' ' must stay below Applications
Error message
brew-cask: {kind} '{name}' must stay below Applications What it means
reject_appdir_escape validates that a staged artifact's relative path stays strictly below the Applications directory: the path must be non-empty and every component must be a plain Normal component. Any '..', '.', root-prefix, or other special component (or an empty path) causes this bail, preventing cask artifacts from being written outside the intended appdir subtree.
Solutions
- Fix the cask artifact's relative path so it is non-empty and contains only plain path components (no '..', '.', or absolute prefixes)
- If the artifact genuinely belongs elsewhere, adjust the cask's appdir/staging root instead of using '..' in the path
- Verify you are using a trusted cask definition — this rejection often indicates a corrupt or malicious stanza
- Re-parse the cask config to ensure empty strings were not produced by a parsing bug
Example fix
// before: escapes Applications
let relative = Path::new("../Utilities/MyApp.app");
// after: stays below Applications
let relative = Path::new("MyApp.app"); Defensive patterns
Strategy: validation
Validate before calling
use std::path::{Component, Path};
fn stays_below_appdir(relative: &Path) -> bool {
!relative.as_os_str().is_empty()
&& relative.components().all(|c| matches!(c, Component::Normal(_)))
} Prevention
- Express cask artifact targets as plain relative paths with no '..', '.', or leading '/'
- Normalize user/registry-supplied paths before writing them into cask stanzas
- Test cask stanza paths with a component check before shipping them
When it happens
Trigger: Staging a cask artifact (app/pkg/binary stanza) whose declared relative target path is empty or contains a non-Normal Path component such as ParentDir ('..'), CurDir ('.'), a RootDir/Prefix, per reject_appdir_escape(relative, kind, name).
Common situations: A cask stanza with a target like '../Utilities/Foo.app' or '/ absolute/path'; empty target strings produced by bad parsing; casks upgraded to a version with a malformed artifact path.
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
- app target is outside an allowed Applications directory
- artifact target has changed
- binary target is not an owned Caskroom symlink
- brew-cask: invalid structured flight path
- brew-cask: refusing generic artifact source outside the…
AI-assisted analysis of jdx/mise@533346cc37 (2026-09-17).
Data as JSON: /api/errors/995f7f5ffe1c86e0.
Report an issue: GitHub.
Appendix: source
Thrown at src/system/packages/brew/cask/mod.rs:3582
let relative = source.strip_prefix(final_caskroom).ok()?;
if relative
.components()
.any(|component| matches!(component, Component::ParentDir))
{
return None;
}
Some(root.join(relative))
}
/// Rejects an empty relative path and any component that could climb out of
/// `$APPDIR` (`..`, a root, a prefix).
fn reject_appdir_escape(relative: &Path, kind: &str, name: &str) -> Result<()> {
if relative.components().next().is_none()
|| relative
.components()
.any(|component| !matches!(component, Component::Normal(_)))
{
bail!("brew-cask: {kind} '{name}' must stay below Applications");
}
Ok(())
}
fn resolve_symlink_target(link: &Path, target: PathBuf) -> PathBuf {
if target.is_absolute() {
target
} else {
link.parent()
.map(|parent| parent.join(&target))
.unwrap_or(target)
}
}
fn durable_internal_symlink_source(stage: &Path, source: &Path, link: &Path) -> Option<PathBuf> {
if !source.is_absolute()
|| staged_relative_path(stage, source).is_none()
|| staged_relative_path(stage, link).is_none()View on GitHub (pinned to 533346cc37)