{"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":1779,"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":1761,"sourceCodeEnd":1797,"githubUrl":"https://github.com/jdx/mise/blob/9dcfcaa0dc8747a2577d3270b69bb9d8313b2807/src/system/packages/brew/cask.rs#L1761-L1797","documentation":"For atomic installs mise opens a private staging directory inside a trusted parent (openat with O_NOFOLLOW) and then fstats it: the directory must be owned by the current euid and have no group/other permission bits (mode & 0o077 == 0). If ownership or permissions are looser — a pre-planted or leftover directory — the install aborts, because anything readable or writable by others could be tampered with between staging and the rename.","triggerScenarios":"A stale staging directory left by a previous mise run under a different user (ran with sudo once, then without, or vice versa), or another process creating the staging name with group/other access inside the trusted parent.","commonSituations":"Switching between root and non-root mise runs on the same machine; shared or kiosk Macs; CI runners reusing $HOME across jobs with different UIDs.","solutions":["Remove mise's brew-cask staging/cache leftovers owned by the other user (under the mise data prefix and the system-brew cache dir), then retry","Pick one identity (always root, or always your user) for mise cask installs on a given machine","If the directory is one you did not create, treat it as suspicious and investigate before deleting"],"exampleFix":null,"handlingStrategy":"validation","validationCode":"use std::os::unix::fs::MetadataExt;\nfn staging_dir_private(p: &Path) -> Result<bool> {\n    let m = p.symlink_metadata()?;\n    Ok(m.uid() == nix::unistd::geteuid().as_raw() && m.mode() & 0o077 == 0)\n}","typeGuard":null,"tryCatchPattern":"Catch 'temporary artifact directory is not private', identify and remove the foreign staging directory, then retry once; if it reappears without explanation, stop and investigate for tampering.","preventionTips":["Run mise cask installs consistently as the same user (never alternate root/non-root)","Don't share mise data/cache dirs across UIDs on multi-user machines"],"tags":["homebrew","cask","permissions","staging","security","mise"],"backgroundTag":"insecure-temp-directory","analyzedSha":"9dcfcaa0dc8747a2577d3270b69bb9d8313b2807","analyzedAt":"2026-08-17T14:28:50.624Z","schemaVersion":2},"datasetVersion":"2026-08-21T18:17:14.833Z"}