{"record":{"id":"3fc2d9f6606c6712","repo":"jdx/mise","slug":"brew-cask-refusing-operation-through-untrusted-di","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.rs","lineNumber":2214,"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":2196,"sourceCodeEnd":2232,"githubUrl":"https://github.com/jdx/mise/blob/9dcfcaa0dc8747a2577d3270b69bb9d8313b2807/src/system/packages/brew/cask.rs#L2196-L2232","documentation":"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.","triggerScenarios":"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).","commonSituations":"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.","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"],"exampleFix":"# before: parent dir is world/group-writable\nls -ld /usr/local/MyApp   # drwxrwxrwx user staff\n\n# after\nsudo chown root:wheel /usr/local/MyApp && sudo chmod 755 /usr/local/MyApp","handlingStrategy":"validation","validationCode":"use std::os::unix::fs::MetadataExt;\n\nfn dir_is_trusted(meta: &std::fs::Metadata, allow_current_user: bool) -> bool {\n    let euid = unsafe { libc::geteuid() } as u32;\n    let trusted_owner = meta.uid() == 0 || (allow_current_user && meta.uid() == euid);\n    let world_writable = meta.mode() & 0o002 != 0;\n    meta.is_dir() && trusted_owner && !world_writable\n}","typeGuard":null,"tryCatchPattern":"match install_cask(&cask) {\n    Err(e) if e.to_string().contains(\"untrusted directory\") => {\n        eprintln!(\"remediate the directory named above: sudo chown root:wheel <dir> && sudo chmod go-w <dir>\");\n        return Err(e);\n    }\n    other => other?,\n}","preventionTips":["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"],"tags":["brew-cask","permissions","ownership","directory-trust","security"],"backgroundTag":"insecure-directory-permissions","analyzedSha":"9dcfcaa0dc8747a2577d3270b69bb9d8313b2807","analyzedAt":"2026-08-17T14:28:50.624Z","schemaVersion":2},"datasetVersion":"2026-08-21T23:17:16.201Z"}