jdx/mise · error · eyre::Report

cached rustc output has an unsafe file mode: {}

Error message

cached rustc output has an unsafe file mode: {}

What it means

validate_file_mode() enforces a security invariant on every restored file (src/cache/rustc.rs:842-859): on Unix a cached node must not be executable (executable false, no 0o111 bits), must have no bits outside 0o777, and must not be group/world-writable (0o022); Windows requires mode 0 and executable false. The publisher only ever stores modes masked to 0o644 (src/cache/rustc.rs:831-834), so a violating entry means cache tampering, a poisoned shared cache, or an entry written by other/older software — restoring it could plant executable or world-writable files, so the restore is refused.

Source

Thrown at src/cache/rustc.rs:848

#[cfg(unix)]
fn file_mode(metadata: &std::fs::Metadata) -> u32 {
    use std::os::unix::fs::PermissionsExt as _;
    metadata.permissions().mode() & 0o644
}

#[cfg(windows)]
fn file_mode(_metadata: &std::fs::Metadata) -> u32 {
    0
}

#[cfg(unix)]
fn validate_file_mode(node: &CacheFileNode) -> Result<()> {
    if node.executable
        || node.mode & !0o777 != 0
        || node.mode & 0o111 != 0
        || node.mode & 0o022 != 0
    {
        bail!("cached rustc output has an unsafe file mode: {}", node.name);
    }
    Ok(())
}

#[cfg(windows)]
fn validate_file_mode(node: &CacheFileNode) -> Result<()> {
    if node.executable || node.mode != 0 {
        bail!("cached rustc output has an unsafe file mode: {}", node.name);
    }
    Ok(())
}

#[cfg(unix)]
fn apply_file_mode(temporary: &Path, mode: u32) -> Result<()> {
    use std::os::unix::fs::PermissionsExt as _;
    std::fs::set_permissions(temporary, std::fs::Permissions::from_mode(mode))?;
    Ok(())
}

View on GitHub (pinned to 6f52dcdf99)

Solutions

  1. Purge the cache and repopulate it from a single trusted build
  2. Do not share remote cache namespaces across tools, teams, or untrusted contributor bases
  3. Treat recurring occurrences as a security signal — the guard exists to block poisoned entries; audit who can write to the namespace
  4. Keep remote tokens scoped and namespaces private per project
Defensive patterns

Strategy: validation

Validate before calling

fn mode_safe(node: &CacheFileNode) -> bool {
    if node.executable {
        return false;
    }
    #[cfg(unix)]
    { node.mode & !0o777 == 0 && node.mode & 0o111 == 0 && node.mode & 0o022 == 0 }
    #[cfg(windows)]
    { node.mode == 0 }
}

Try / catch

On any unsafe-mode entry, discard the entry and the surrounding cache and recompile from trusted sources; do not 'fix' permissions on restored files and keep them — the mode is part of the cached content's integrity contract.

Prevention

When it happens

Trigger: A crafted CAS entry carrying exec/setuid bits or group-write permissions; a cache populated by a different tool sharing the CAS layout; an older mise build that stored unmasked permission bits.

Common situations: Public or broadly shared remote cache namespaces accepting entries from untrusted contributors; a compromised CI cache; mixed tooling writing into one CAS.

Related errors


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