jdx/mise · error
brew-cask: invalid generic artifact parent
Error message
brew-cask: invalid generic artifact parent
What it means
While opening the generic artifact's parent path component by component, only Component::Normal names are accepted. Any other component (ParentDir '..', CurDir '.', RootDir '/', or a disk prefix) makes the path invalid for a trusted parent, because such paths cannot be safely walked with openat relative to the trusted prefix root.
Source
Thrown at src/system/packages/brew/cask.rs:2225
|| current_groups.contains(&stat.st_gid);
let writable_by_untrusted = stat.st_mode & 0o002 != 0
|| (stat.st_mode & 0o020 != 0 && (!allow_current_user || !trusted_group));
if !SFlag::from_bits_truncate(stat.st_mode).contains(SFlag::S_IFDIR)
|| !trusted_owner
|| writable_by_untrusted
{
bail!(
"brew-cask: refusing operation through untrusted directory {}",
directory.display()
);
}
Ok(())
};
let mut directory = resolved_root.to_path_buf();
verify(&fd, &directory)?;
for component in relative.components() {
let Component::Normal(name) = component else {
bail!("brew-cask: invalid generic artifact parent");
};
directory.push(name);
fd = match openat(&fd, name, flags, Mode::empty()) {
Ok(fd) => fd,
Err(nix::errno::Errno::ENOENT) if create_missing => {
match nix::sys::stat::mkdirat(
&fd,
name,
Mode::S_IRWXU | Mode::S_IRGRP | Mode::S_IXGRP | Mode::S_IROTH | Mode::S_IXOTH,
) {
Ok(()) | Err(nix::errno::Errno::EEXIST) => {}
Err(err) => {
return Err(err).wrap_err_with(|| {
format!(
"brew-cask: cannot create operation directory {}",
directory.display()
)
});View on GitHub (pinned to 9dcfcaa0dc)
Solutions
- Normalize the parent path before use: resolve '.'/'..' with a path-cleaning step or canonicalize the existing prefix portion
- Fix the cask/config so the parent is a plain sequence of names relative to the Homebrew prefix
- Validate user-supplied parent strings with a components() check and reject non-normal components early
Example fix
// before
let parent = format!("{}/../lib", prefix);
// after -- build with Path and assert no traversal
let parent = prefix.join("lib");
assert!(!parent.components().any(|c| matches!(c, std::path::Component::ParentDir))); Defensive patterns
Strategy: validation
Validate before calling
use std::path::{Path, Component};
fn has_only_normal_components(path: &Path) -> bool {
!path.is_absolute()
&& path.components().next().is_some()
&& path.components().all(|c| matches!(c, Component::Normal(_)))
} Type guard
fn is_normal_relative_path(p: &std::path::Path) -> bool {
p.components().all(|c| matches!(c, std::path::Component::Normal(_)))
} Prevention
- Never build parent paths with '..' via string concatenation; use Path::join
- Canonicalize or path-clean user-supplied parents before passing them to artifact handlers
- Reject ParentDir/CurDir/RootDir components early in validation
When it happens
Trigger: Passing a parent path containing '..', '.', or a leading '/' into the generic artifact parent resolution; parent strings built by string concatenation with unvalidated user input instead of Path::join.
Common situations: Cask stanzas or config with relative parent paths like "../shared/lib"; code that builds paths with format!() and never normalizes; templates leaving placeholder dot components behind.
Related errors
- mise upgrade --monorepo is not implemented yet
- brew-cask: refusing generic artifact copy outside Homebrew p
- brew-cask: refusing installer executable outside trusted ins
- brew-cask: generic artifact target '{}' must stay below {}
- brew-cask: invalid font target '{}'
AI-assisted analysis of jdx/mise@9dcfcaa0dc (2026-08-17).
Data as JSON: /api/errors/22586603ae5e32f0.
Report an issue: GitHub.