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

  1. Check ownership/permissions on the caskroom target's parent directory (ls -la) and fix so your user owns it with sane modes (0700/0755)
  2. Verify the filesystem backing the caskroom is a normal local filesystem, not a mount that alters modes/uids (NFS, bind mounts, some overlayfs setups)
  3. Re-run the install; if it persists, inspect for other local users/processes interfering with the caskroom
  4. 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

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


AI-assisted analysis of jdx/mise@afd2eddd3a (2026-09-09). Data as JSON: /api/errors/0ac05c3adc83393e. Report an issue: GitHub.