jdx/mise · error
brew-cask: refusing operation through untrusted directory {}
Error message
brew-cask: refusing operation through untrusted directory {} What it means
mise walks each component of the target path with openat and verifies every directory along the way before writing through it. A directory is trusted only if it is actually a directory, owned by root (or, when allow_current_user is set, by the invoking or sudo user), and not writable by untrusted groups or the world. This blocks symlink-swap attacks where anyone with write access to an ancestor could redirect the operation.
Source
Thrown at src/system/packages/brew/cask.rs:2214
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 9dcfcaa0dc)
Solutions
- Fix ownership of the directory named in the error: sudo chown root:wheel <dir> (or chown $(whoami) for per-user appdirs like ~/Applications)
- Remove untrusted write bits: chmod go-w <dir>
- Prefer standard prefixes (/opt/homebrew, /usr/local) with brew's ownership model instead of custom user-writable prefixes
- Run cask install/uninstall as the same user that owns the prefix: do not mix sudo and non-sudo
Example fix
# before: parent dir is world/group-writable ls -ld /usr/local/MyApp # drwxrwxrwx user staff # after sudo chown root:wheel /usr/local/MyApp && sudo chmod 755 /usr/local/MyApp
Defensive patterns
Strategy: validation
Validate before calling
use std::os::unix::fs::MetadataExt;
fn dir_is_trusted(meta: &std::fs::Metadata, allow_current_user: bool) -> bool {
let euid = unsafe { libc::geteuid() } as u32;
let trusted_owner = meta.uid() == 0 || (allow_current_user && meta.uid() == euid);
let world_writable = meta.mode() & 0o002 != 0;
meta.is_dir() && trusted_owner && !world_writable
} Try / catch
match install_cask(&cask) {
Err(e) if e.to_string().contains("untrusted directory") => {
eprintln!("remediate the directory named above: sudo chown root:wheel <dir> && sudo chmod go-w <dir>");
return Err(e);
}
other => other?,
} Prevention
- Never chmod 777 directories inside a Homebrew prefix
- Keep the prefix root-owned on multi-user machines
- Do not run cask operations under sudo when the prefix is user-owned, or vice versa
- Audit ownership after migrating or restoring a prefix from backup
When it happens
Trigger: A parent directory of the artifact target that is world-writable (mode & 0o002) or group-writable with an untrusted group; a component owned by another non-root user while allow_current_user is false; a path component that is not a directory (S_IFDIR missing).
Common situations: Homebrew prefix installed in a user-writable location (~/homebrew) while the cask operation runs as root; directories created with 0777 by scripts or restore processes; group-writable shared dirs (e.g. staff) in the target path on macOS.
Related errors
- brew-cask: staging directory is not owned by the current use
- brew-cask: temporary artifact directory is not private
- brew-cask: refusing elevated operation through mutable direc
- brew-cask: temporary artifact directory was replaced
- brew-cask: refusing installer executable outside trusted ins
AI-assisted analysis of jdx/mise@9dcfcaa0dc (2026-08-17).
Data as JSON: /api/errors/3fc2d9f6606c6712.
Report an issue: GitHub.