{"record":{"id":"3fcbbcf41f9ba03a","repo":"jdx/mise","slug":"brew-cask-temporary-artifact-directory-is-not-pri","errorCode":null,"errorMessage":"brew-cask: temporary artifact directory is not private","messagePattern":"brew-cask: temporary artifact directory is not private","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"critical","filePath":"src/system/packages/brew/cask.rs","lineNumber":1905,"sourceCode":"        .ok_or_else(|| eyre!(\"brew-cask: generic artifact target has no filename\"))?;\n    let staging_name = format!(\".mise-copy-{}\", crate::rand::random_string(16));\n    nix::sys::stat::mkdirat(\n        &parent.fd,\n        staging_name.as_str(),\n        nix::sys::stat::Mode::S_IRWXU,\n    )?;\n    let flags = nix::fcntl::OFlag::O_RDONLY\n        | nix::fcntl::OFlag::O_DIRECTORY\n        | nix::fcntl::OFlag::O_NOFOLLOW;\n    let staging_fd = nix::fcntl::openat(\n        &parent.fd,\n        staging_name.as_str(),\n        flags,\n        nix::sys::stat::Mode::empty(),\n    )?;\n    let staging_stat = nix::sys::stat::fstat(&staging_fd)?;\n    if staging_stat.st_uid != nix::unistd::geteuid().as_raw() || staging_stat.st_mode & 0o077 != 0 {\n        bail!(\"brew-cask: temporary artifact directory is not private\");\n    }\n    let staging = TrustedOperationParent { fd: staging_fd };\n    let temporary_name = std::ffi::OsStr::new(\"payload\");\n    match copy_cask_artifact_at(from, &staging.fd, temporary_name) {\n        Ok(()) => {\n            match nix::fcntl::renameat(&staging.fd, temporary_name, &parent.fd, name)\n                .wrap_err_with(|| format!(\"failed to install {}\", to.display()))\n            {\n                Ok(()) => {\n                    remove_private_staging_dir(&parent, &staging, staging_name.as_ref())?;\n                    Ok(())\n                }\n                Err(err) => {\n                    remove_all_at(&staging.fd, temporary_name).wrap_err_with(|| {\n                            format!(\n                                \"failed to clean up temporary generic artifact after rename failed: {err:#}\"\n                            )\n                        })?;","sourceCodeStart":1887,"sourceCodeEnd":1923,"githubUrl":"https://github.com/jdx/mise/blob/6f52dcdf99e282ef7a7db68c81301fa4618d0f79/src/system/packages/brew/cask.rs#L1887-L1923","documentation":"Privileged artifact copies stage through a temporary directory opened with O_NOFOLLOW and then fstat-verified (src/system/packages/brew/cask.rs:1905): the staging directory must be owned by the effective user and have zero group/other permission bits (mode & 0o077 == 0). If a different uid owns it or it is group/world-accessible, mise aborts — defeating symlink-swap and snooping races on a directory that elevated (sudo) operations write through.","triggerScenarios":"A pre-existing or raced staging directory under the target's parent that is owned by another uid or carries permissive bits — e.g. a root-created 0755 staging dir when mise runs as the user, or a group-writable 0770 dir an attacker could populate.","commonSituations":"Leftover staging directories from crashed privileged installs; parents with loose permissions adjacent to /Applications; multi-admin machines where another admin's run left differently-owned dirs; deliberate hardening probes in security-sensitive environments.","solutions":["Remove the stale staging directory (under the target's parent, from the failed run) and retry the install","Check the parent directory chain's ownership and permissions — elevated copies require root-owned, non-group/world-writable parents","If it recurs, find the process creating those dirs with wrong ownership (concurrent brew/mise runs, broken sudo wrappers) and serialize or fix it"],"exampleFix":null,"handlingStrategy":"validation","validationCode":"use std::os::unix::fs::MetadataExt;\nlet md = std::fs::symlink_metadata(&staging_dir)?;\nif md.uid() != nix::unistd::geteuid().as_raw() || md.mode() & 0o077 != 0 {\n    return Err(anyhow!(\"staging directory is not private - refusing privileged copy\"));\n}","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Clean stale staging directories after crashed privileged installs","Keep parents of privileged targets root-owned and not group-writable","Serialize installs so staging directories are never created by a different uid"],"tags":["brew-cask","security","permissions","temp-dir","privilege-escalation"],"backgroundTag":"insecure-temp-directory","analyzedSha":"6f52dcdf99e282ef7a7db68c81301fa4618d0f79","analyzedAt":"2026-08-22T10:14:23.840Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}