jdx/mise · error
brew-cask: refusing operation through untrusted directory
Error message
brew-cask: refusing operation through untrusted directory {} What it means
open_trusted_directory walks from the desymlinked Homebrew prefix to the artifact's parent one component at a time with O_NOFOLLOW and verifies every directory it opens. verify() rejects a component that is not a directory, is not owned by root (or by the invoking/sudo user when allow_current_user), or is writable by an untrusted party — world-writable (0o002), or group-writable (0o020) by a group that is neither the effective gid, a supplemental group, nor the sudo gid. This blocks symlink interposition and privilege escalation through mutable directories.
Solutions
- Walk `ls -ld` from the Homebrew prefix down to the printed path to find the loose or foreign-owned directory
- Fix it: sudo chown root:admin <dir> (or chown to the invoking user for per-user dirs) and sudo chmod go-w <dir>
- Re-run the cask install
- If a tool legitimately needs group write, make the group one the installing user belongs to so trusted_group passes
Example fix
# before $ ls -ld /opt/homebrew/etc drwxrwxrwx ... # after $ sudo chown root:admin /opt/homebrew/etc && sudo chmod go-w /opt/homebrew/etc
Defensive patterns
Strategy: validation
Validate before calling
// Preflight: every component from the prefix down must be a trusted dir.
fn prefix_chain_is_trusted(prefix: &std::path::Path) -> std::io::Result<()> {
use std::os::unix::fs::MetadataExt;
let uid = nix_uid(); // your effective uid
let mut cur = std::path::PathBuf::new();
for comp in prefix.components() {
cur.push(comp);
let md = std::fs::symlink_metadata(&cur)?;
if md.file_type().is_symlink() || !md.is_dir() { continue_or_fail()?; }
if !(md.uid() == 0 || md.uid() == uid) || md.mode() & 0o002 != 0 {
return Err(std::io::Error::new(std::io::ErrorKind::PermissionDenied,
format!("untrusted directory: {}", cur.display())));
}
}
Ok(())
} Type guard
fn is_trusted_dir_owner(md: &std::fs::Metadata, euid: u32) -> bool {
use std::os::unix::fs::MetadataExt;
md.uid() == 0 || md.uid() == euid
} Prevention
- Never chmod 777 anything under the Homebrew prefix
- Audit ownership after admin account changes: chown -R root:admin (or user:admin) under the prefix
- Decide once whether casks install elevated or as the user, and stay consistent so SUDO_UID checks line up
When it happens
Trigger: A world-writable directory anywhere on the chain from the prefix to the artifact parent; a directory owned by another non-root user when allow_current_user is false; group-writable with an untrusted group (e.g. running unprivileged under a different account than the dir owner with SUDO_UID/GID not matching); a component that opened as a non-directory.
Common situations: Someone ran chmod 777 on /opt/homebrew/etc or a parent; prefix dirs left owned by a former admin account; mixing sudo and non-sudo installs so SUDO_UID doesn't match the dir owner; directories created by other package managers with loose group-write bits.
Related errors
- brew-cask: completion target
- brew-cask: refusing elevated operation through mutable…
- brew-cask: refusing elevated operation through mutable…
- brew-cask: refusing generic artifact copy outside Homebrew…
- brew-cask: refusing operation through untrusted directory
AI-assisted analysis of jdx/mise@6f52dcdf99 (2026-08-22).
Data as JSON: /api/errors/3fc2d9f6606c6712.
Report an issue: GitHub.
Appendix: source
Thrown at src/system/packages/brew/cask.rs:2340
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 6f52dcdf99)