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

  1. Walk `ls -ld` from the Homebrew prefix down to the printed path to find the loose or foreign-owned directory
  2. Fix it: sudo chown root:admin <dir> (or chown to the invoking user for per-user dirs) and sudo chmod go-w <dir>
  3. Re-run the cask install
  4. 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

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


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)