jdx/mise · error
brew-cask: refusing operation through untrusted directory {}
Error message
brew-cask: refusing operation through untrusted directory {} What it means
This check walks each directory on the operation's path and refuses to proceed if any component is not a trusted directory. A directory is trusted only if it is owned by a trusted owner (root or, when allowed, the current user), is a real directory (not a symlink), is not world-writable (`0o002` clear), and is not group-writable by an untrusted group (`0o020` clear unless the owning group is trusted and the current user counts as trusted). This blocks symlink/permission-based attacks where an unprivileged user could hijack a path component and redirect privileged writes.
Source
Thrown at src/system/packages/brew/cask/mod.rs:2062
let current_uid = nix::unistd::geteuid().as_raw();
let current_gid = nix::unistd::getegid().as_raw();
let current_groups = current_process_groups()?;
let sudo_uid = sudo_invoking_id(current_uid, "SUDO_UID");
let sudo_gid = sudo_invoking_id(current_uid, "SUDO_GID");
let verify = |fd: &std::os::fd::OwnedFd, directory: &Path| -> Result<()> {
let stat = fstat(fd)?;
let owner_is_user = stat.st_uid == current_uid || Some(stat.st_uid) == sudo_uid;
let trusted_owner = stat.st_uid == 0 || (allow_current_user && owner_is_user);
let trusted_group = stat.st_gid == current_gid
|| Some(stat.st_gid) == sudo_gid
|| 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,View on GitHub (pinned to afd2eddd3a)
Solutions
- Identify the offending directory from the message and tighten its permissions: `chmod o-w <dir>` (and `chmod g-w` if group-writable by an untrusted group).
- Ensure the directory is owned by root or the current user: `sudo chown <owner> <dir>`.
- Replace any symlink in the path with the real directory, since symlink components are rejected.
- Reinstall or relocate the Homebrew prefix so all ancestor directories are root-owned and not writable by other users (the standard Homebrew permission model).
Example fix
// before: inspect a world-writable prefix component drwxrwxrwx /opt/homebrew // after: fix ownership and permissions so the trust check passes sudo chown root:admin /opt/homebrew sudo chmod 755 /opt/homebrew
Defensive patterns
Strategy: validation
Validate before calling
use std::os::unix::fs::MetadataExt;
use std::path::Path;
fn dir_is_trusted(p: &Path, uid: u32, trusted_gids: &[u32]) -> bool {
std::fs::metadata(p).map(|md| {
let mode = md.mode();
md.is_dir()
&& !p.is_symlink()
&& (md.uid() == 0 || md.uid() == uid)
&& mode & 0o002 == 0
&& (mode & 0o020 == 0 || trusted_gids.contains(&md.gid()))
}).unwrap_or(false)
}
Type guard
fn is_trusted_dir(p: &std::path::Path) -> bool {
use std::os::unix::fs::MetadataExt;
std::fs::metadata(p).map(|m| {
m.is_dir() && m.mode() & 0o002 == 0 && m.mode() & 0o020 == 0
}).unwrap_or(false)
}
Prevention
- Keep the brew prefix and all parent directories root-owned with mode 755.
- Never grant o+w or untrusted g+w on any component of the install path.
- Avoid symlinks in the install path; use real directories.
- On multi-user machines, follow the standard Homebrew ownership model (prefix owned by root, subdirs by admin).
When it happens
Trigger: A brew-cask elevated operation whose path traverses a directory that fails the trust test: world-writable (mode `o+w`), group-writable by a group not trusted for the current user, a symlink instead of a real directory, or owned by an unexpected user — detected by the `stat`-based closure around mod.rs:2062.
Common situations: A shared machine where the Homebrew prefix or a parent (e.g. `/opt`, `/usr/local`) is group-writable; a home-directory prefix on a multi-user system with a permissive umask; symlinked prefix paths after migrating Homebrew; running as a user whose primary group differs from the prefix's group.
Understand the failure class
Background: Permission denied / not authorized / 403 Forbidden: access-control rejections when the caller lacks the required role, grant, or ownership — this error's family across 18 libraries.
Related errors
- brew-cask: staged symlink path escaped extraction root: {}
- brew-cask: refusing generic artifact source outside the extr
- expected world-writable ancestor to be refused
- expected symlinked appdir tail to be rejected
- refusing to follow symlink {} from an untrusted parent direc
AI-assisted analysis of jdx/mise@afd2eddd3a (2026-09-09).
Data as JSON: /api/errors/cfb14e0d65a7feb5.
Report an issue: GitHub.