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
- Purge the cache and repopulate it from a single trusted build
- Do not share remote cache namespaces across tools, teams, or untrusted contributor bases
- Treat recurring occurrences as a security signal — the guard exists to block poisoned entries; audit who can write to the namespace
- 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
- Keep remote cache namespaces private and access-controlled
- Do not share one CAS across tools or untrusted contributor bases
- Audit recurring unsafe-mode warnings as potential cache poisoning, not noise
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
- cached rustc output set does not match the invocation
- GitHub Actions OIDC request URL must use HTTPS
- remote cache URL must use HTTPS except for loopback developm
- cached rustc action is missing blob {}
- cache agent returned an incomplete blob lookup response
AI-assisted analysis of jdx/mise@6f52dcdf99 (2026-08-22).
Data as JSON: /api/errors/0a9320d0cd880489.
Report an issue: GitHub.