{"record":{"id":"cfb14e0d65a7feb5","repo":"jdx/mise","slug":"brew-cask-refusing-operation-through-untrusted-di-cfb14e","errorCode":null,"errorMessage":"brew-cask: refusing operation through untrusted directory {}","messagePattern":"brew-cask: refusing operation through untrusted directory (.+?)","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"src/system/packages/brew/cask/mod.rs","lineNumber":2062,"sourceCode":"    let current_uid = nix::unistd::geteuid().as_raw();\n    let current_gid = nix::unistd::getegid().as_raw();\n    let current_groups = current_process_groups()?;\n    let sudo_uid = sudo_invoking_id(current_uid, \"SUDO_UID\");\n    let sudo_gid = sudo_invoking_id(current_uid, \"SUDO_GID\");\n    let verify = |fd: &std::os::fd::OwnedFd, directory: &Path| -> Result<()> {\n        let stat = fstat(fd)?;\n        let owner_is_user = stat.st_uid == current_uid || Some(stat.st_uid) == sudo_uid;\n        let trusted_owner = stat.st_uid == 0 || (allow_current_user && owner_is_user);\n        let trusted_group = stat.st_gid == current_gid\n            || Some(stat.st_gid) == sudo_gid\n            || current_groups.contains(&stat.st_gid);\n        let writable_by_untrusted = stat.st_mode & 0o002 != 0\n            || (stat.st_mode & 0o020 != 0 && (!allow_current_user || !trusted_group));\n        if !SFlag::from_bits_truncate(stat.st_mode).contains(SFlag::S_IFDIR)\n            || !trusted_owner\n            || writable_by_untrusted\n        {\n            bail!(\n                \"brew-cask: refusing operation through untrusted directory {}\",\n                directory.display()\n            );\n        }\n        Ok(())\n    };\n    let mut directory = resolved_root.to_path_buf();\n    verify(&fd, &directory)?;\n    for component in relative.components() {\n        let Component::Normal(name) = component else {\n            bail!(\"brew-cask: invalid generic artifact parent\");\n        };\n        directory.push(name);\n        fd = match openat(&fd, name, flags, Mode::empty()) {\n            Ok(fd) => fd,\n            Err(nix::errno::Errno::ENOENT) if create_missing => {\n                match nix::sys::stat::mkdirat(\n                    &fd,","sourceCodeStart":2044,"sourceCodeEnd":2080,"githubUrl":"https://github.com/jdx/mise/blob/afd2eddd3a50c16190efc1c7e94404b48f72af57/src/system/packages/brew/cask/mod.rs#L2044-L2080","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","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)."],"exampleFix":"// before: inspect a world-writable prefix component\ndrwxrwxrwx  /opt/homebrew\n// after: fix ownership and permissions so the trust check passes\nsudo chown root:admin /opt/homebrew\nsudo chmod 755 /opt/homebrew","handlingStrategy":"validation","validationCode":"use std::os::unix::fs::MetadataExt;\nuse std::path::Path;\nfn dir_is_trusted(p: &Path, uid: u32, trusted_gids: &[u32]) -> bool {\n    std::fs::metadata(p).map(|md| {\n        let mode = md.mode();\n        md.is_dir()\n            && !p.is_symlink()\n            && (md.uid() == 0 || md.uid() == uid)\n            && mode & 0o002 == 0\n            && (mode & 0o020 == 0 || trusted_gids.contains(&md.gid()))\n    }).unwrap_or(false)\n}\n","typeGuard":"fn is_trusted_dir(p: &std::path::Path) -> bool {\n    use std::os::unix::fs::MetadataExt;\n    std::fs::metadata(p).map(|m| {\n        m.is_dir() && m.mode() & 0o002 == 0 && m.mode() & 0o020 == 0\n    }).unwrap_or(false)\n}\n","tryCatchPattern":null,"preventionTips":["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)."],"tags":["security","permissions","path-traversal","symlink"],"backgroundTag":"permission-denied","analyzedSha":"afd2eddd3a50c16190efc1c7e94404b48f72af57","analyzedAt":"2026-09-09T01:38:25.179Z","contentChangedAt":"2026-09-09T01:38:25.179Z","schemaVersion":2},"datasetVersion":"2026-09-14T00:17:10.932Z"}