jdx/mise · critical

brew-cask: refusing elevated operation through mutable…

Error message

brew-cask: refusing elevated operation through mutable directory {}

What it means

validate_strict_elevated_ancestors (src/system/packages/brew/cask.rs:2010) walks every ancestor of a path a sudo operation will write through and requires each to be trusted: root-owned (uid 0), not world-writable, and group-writable only for the exact Homebrew prefix itself (the Intel root:admin 0775 /usr/local exception in strict_elevated_directory_is_trusted). Any mutable directory — user-owned, group-writable, or reached via untrusted symlinks — aborts the privileged copy.

Solutions

  1. Install to the default locations mise/Homebrew use — root-owned ancestors with no group/world write — instead of custom writable targets
  2. Tighten the offending directory named in the error: sudo chown root <dir> && sudo chmod go-w <dir>
  3. Replace user-owned symlinks in the ancestor chain with real directories (desymlink the path)
  4. If the dir is Intel brew's 0775 prefix, ensure it is exactly the configured brew prefix — the exception applies only there

Example fix

# before
$ sudo chmod -R g+w /opt/tools   # ancestors became group-writable
$ mise install brew-cask:foo    # refuses elevated operation through /opt/tools

# after
$ sudo chown root /opt/tools && sudo chmod go-w /opt/tools
$ mise install brew-cask:foo
Defensive patterns

Strategy: validation

Validate before calling

for dir in target.ancestors() {
    let md = dir.symlink_metadata()?;
    let trusted = md.uid() == 0 && md.mode() & 0o002 == 0;
    if !trusted {
        return Err(anyhow!("mutable ancestor {} - refusing elevated operation", dir.display()));
    }
}

Prevention

When it happens

Trigger: A cask artifact target whose path traverses a user-owned or group/world-writable directory: custom targets pointing into /Users/.../bin or a chmod 0777/0775 dir, or symlinked components resolving into untrusted locations during the ancestor walk.

Common situations: Custom artifact target paths in cask stanzas on shared machines; directories loosened to 0775/0777 for convenience; Intel Macs where /usr/local subdirectories were chowned to the admin user; relocated brew prefixes outside the trusted prefix.

Related errors


AI-assisted analysis of jdx/mise@6f52dcdf99 (2026-08-22). Data as JSON: /api/errors/0646ed89044d1755. Report an issue: GitHub.

Appendix: source

Thrown at src/system/packages/brew/cask.rs:2010

        target
            .file_name()
            .ok_or_else(|| eyre!("brew-cask: generic artifact target has no filename"))?,
    ))
}

#[cfg(unix)]
fn validate_strict_elevated_ancestors(path: &Path) -> Result<()> {
    use std::os::unix::fs::MetadataExt;
    let stable_prefix = file::desymlink_path(&prefix::prefix());
    for directory in path.ancestors() {
        let metadata = directory.symlink_metadata()?;
        if !strict_elevated_directory_is_trusted(
            directory,
            &stable_prefix,
            metadata.uid(),
            metadata.mode(),
        ) {
            bail!(
                "brew-cask: refusing elevated operation through mutable directory {}",
                directory.display()
            );
        }
    }
    Ok(())
}

#[cfg(unix)]
fn strict_elevated_directory_is_trusted(
    directory: &Path,
    stable_prefix: &Path,
    uid: u32,
    mode: u32,
) -> bool {
    uid == 0
        && mode & 0o002 == 0
        // Intel Homebrew conventionally uses root:admin 0775 for /usr/local.

View on GitHub (pinned to 6f52dcdf99)