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

  1. Fix ownership of the directory named in the error: sudo chown root:wheel <dir> (or chown $(whoami) for per-user appdirs like ~/Applications)
  2. Remove untrusted write bits: chmod go-w <dir>
  3. Prefer standard prefixes (/opt/homebrew, /usr/local) with brew's ownership model instead of custom user-writable prefixes
  4. 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

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


AI-assisted analysis of jdx/mise@9dcfcaa0dc (2026-08-17). Data as JSON: /api/errors/3fc2d9f6606c6712. Report an issue: GitHub.