jdx/mise · error
brew-cask: temporary artifact directory is not private
Error message
brew-cask: temporary artifact directory is not private
What it means
When copying a generic artifact unprivileged, mise creates a private staging directory (mode 0700) next to the target and then verifies via fstat that the opened directory is owned by the effective UID and has no group/other permission bits. If the directory's owner or mode does not match (uid != euid or mode & 0o077 != 0), the copy is aborted — this guards against a pre-existing or tampered directory letting another local user read the payload or swap in files during the copy/rename.
Source
Thrown at src/system/packages/brew/cask/mod.rs:1625
}
}
#[cfg(unix)]
fn copy_generic_artifact_unprivileged(from: &Path, to: &Path) -> Result<()> {
let parent = open_trusted_operation_parent(to, true, true)?;
let name = to
.file_name()
.ok_or_else(|| eyre!("brew-cask: generic artifact target has no filename"))?;
let staging_name = format!(".mise-copy-{}", crate::rand::random_string(16));
nix::sys::stat::mkdirat(
&parent.fd,
staging_name.as_str(),
nix::sys::stat::Mode::S_IRWXU,
)?;
let staging_fd = open_dir_nofollow_at(&parent.fd, staging_name.as_str())?;
let staging_stat = nix::sys::stat::fstat(&staging_fd)?;
if staging_stat.st_uid != nix::unistd::geteuid().as_raw() || staging_stat.st_mode & 0o077 != 0 {
bail!("brew-cask: temporary artifact directory is not private");
}
let staging = TrustedOperationParent { fd: staging_fd };
let temporary_name = std::ffi::OsStr::new("payload");
match copy_cask_artifact_at(from, &staging.fd, temporary_name) {
Ok(()) => {
match nix::fcntl::renameat(&staging.fd, temporary_name, &parent.fd, name)
.wrap_err_with(|| format!("failed to install {}", to.display()))
{
Ok(()) => {
remove_private_staging_dir(&parent, &staging, staging_name.as_ref())?;
Ok(())
}
Err(err) => {
remove_all_at(&staging.fd, temporary_name).wrap_err_with(|| {
format!(
"failed to clean up temporary generic artifact after rename failed: {err:#}"
)
})?;View on GitHub (pinned to afd2eddd3a)
Solutions
- Check ownership/permissions on the caskroom target's parent directory (ls -la) and fix so your user owns it with sane modes (0700/0755)
- Verify the filesystem backing the caskroom is a normal local filesystem, not a mount that alters modes/uids (NFS, bind mounts, some overlayfs setups)
- Re-run the install; if it persists, inspect for other local users/processes interfering with the caskroom
- Ensure mise runs as the same user that owns the installation directories (avoid mixing sudo and non-sudo installs)
Example fix
// before (mixed-ownership caskroom after a sudo install) sudo mise use cask:<name> // after mise use cask:<name> # run as the owning user; chown -R $(id -u) the caskroom if needed
Defensive patterns
Strategy: validation
Validate before calling
// pre-check the target parent dir before invoking the install
let md = std::fs::metadata(target.parent().unwrap())?;
if md.uid() != nix::unistd::geteuid().as_raw() || md.mode() & 0o077 != 0 {
return Err(format!("{} is not private or not owned by you", target.display()));
} Prevention
- Always run mise as the same user that owns the install directories
- Avoid installing to directories on network/overlay filesystems that rewrite modes or uids
- Never mix sudo and non-sudo cask installs against the same paths
- Periodically audit caskroom permissions (find ~/.mise -not -user $(id -un))
When it happens
Trigger: copy_generic_artifact_unprivileged creates a .mise-copy-XXXX staging dir via mkdirat with S_IRWXU, opens it nofollow, and fstat shows either an owner different from the effective user or permission bits for group/other set. This would indicate filesystem-level tampering, an unusual umask/ACL effect, or a compromised parent directory.
Common situations: Running on a system where another user or a setuid-hijacked process manipulates the target directory; exotic mounts (some network/container filesystems) that do not honor requested modes or rewrite ownership; overly permissive umask handling or ACLs on the target's parent directory.
Understand the failure class
Background: Permission denied / not authorized / 403 Forbidden: access-control rejections when the caller lacks the required role, grant, or ownership — this error's family across 18 libraries.
Related errors
- brew-cask: temporary artifact directory is not private
- brew-cask: refusing elevated operation through mutable direc
- brew-cask: refusing operation through untrusted directory {}
- expected world-writable ancestor to be refused
- directory changed while restoring permissions: {}
AI-assisted analysis of jdx/mise@afd2eddd3a (2026-09-09).
Data as JSON: /api/errors/0ac05c3adc83393e.
Report an issue: GitHub.