jdx/mise · error
expected world-writable ancestor to be refused
Error message
expected world-writable ancestor to be refused
What it means
Test panic in the cask security tests: ensure_trusted_appdir was given an Applications path whose ancestor directory is world-writable (mode 01777), and it returned Ok instead of refusing. The test expects an error whose message contains 'untrusted directory', because staging app content under a world-writable ancestor would let any local user tamper with the target.
Source
Thrown at src/system/packages/brew/cask/tests.rs:6651
appdir.join("Foo.app/Contents/MacOS/foo"),
);
Ok(())
}
#[test]
fn ensure_trusted_appdir_refuses_world_writable_ancestor() -> Result<()> {
// Regression guard for the CI failure: a world-writable ancestor (as
// `/tmp` is, mode 1777) must be refused, because any local user could
// substitute components beneath it. Real application directories are
// never world-writable.
let tmp = trusted_tempdir()?;
let base = tmp.path().canonicalize()?;
let shared = base.join("shared");
file::create_dir_all(&shared)?;
let mode = std::fs::Permissions::from_mode(0o1777);
std::fs::set_permissions(&shared, mode)?;
let err = match ensure_trusted_appdir(&shared.join("Applications")) {
Ok(_) => panic!("expected world-writable ancestor to be refused"),
Err(err) => err.to_string(),
};
assert!(err.contains("untrusted directory"), "{err}");
Ok(())
}
#[test]
fn ensure_trusted_appdir_creates_missing_tail() -> Result<()> {
let tmp = trusted_tempdir()?;
let base = tmp.path().canonicalize()?;
let appdir = base.join("Applications");
ensure_trusted_appdir(&appdir)?;
assert!(appdir.symlink_metadata()?.file_type().is_dir());
// Idempotent when the directory already exists.
ensure_trusted_appdir(&appdir)?;
Ok(())
}
View on GitHub (pinned to afd2eddd3a)
Solutions
- Run the test to confirm ensure_trusted_appdir succeeds when it must fail
- In ensure_trusted_appdir, walk each ancestor and reject with an 'untrusted directory' error when the mode's group/other write bits are set (allowing only the sticky case the implementation intends to trust)
- Ensure canonicalization of the base path doesn't mask ancestor permission checks
- Re-run the test and confirm the error message contains 'untrusted directory'
Example fix
// before (regressed)
if !dir.is_dir() { return Err(...); } // no permission check
// after
let mode = std::fs::metadata(dir)?.permissions().mode();
if mode & 0o022 != 0 {
return Err(anyhow!("untrusted directory {}: group/other writable", dir.display()));
} Defensive patterns
Strategy: validation
Validate before calling
// caller-side guard before staging into an appdir
fn ancestor_modes_ok(path: &Path) -> std::io::Result<bool> {
for anc in path.ancestors().skip(1) {
let mode = std::fs::metadata(anc)?.permissions().mode();
if mode & 0o022 != 0 { return Ok(false); } // group/other writable: untrusted
}
Ok(true)
}
assert!(ancestor_modes_ok(&shared.join("Applications"))?); Type guard
fn is_trusted_dir(p: &Path) -> bool {
std::fs::metadata(p).map(|m| m.is_dir() && m.permissions().mode() & 0o022 == 0).unwrap_or(false)
} Try / catch
match ensure_trusted_appdir(&appdir) {
Ok(()) => { /* proceed */ }
Err(e) if e.to_string().contains("untrusted directory") => {
eprintln!("refusing to stage into world-writable ancestor: {e}");
}
Err(e) => return Err(e),
} Prevention
- Check every ancestor's permission bits, not just the leaf directory
- Canonicalize paths before checking so symlinked ancestors are evaluated
- Fail closed: refuse rather than warn when trust checks cannot run
When it happens
Trigger: Calling ensure_trusted_appdir(path) where any ancestor directory of path is world-writable (sticky 0o1777) and the function fails to detect/reject it, returning Ok(()).
Common situations: Users on shared machines with /tmp-style shared directories in the appdir path; regression in the ancestor trust check (e.g. permissions checks dropped during refactor, or running as a user that bypasses the check).
Understand the failure class
Background: "You do not have permission" / 403 Forbidden errors: authenticated but not allowed — causes and fixes across open-source libraries — this error's family across 31 libraries.
Related errors
- expected symlinked appdir tail to be rejected
- expected pre-planted symlink destination to be refused
- refusing to follow symlink {} from an untrusted parent direc
- created path component {} was replaced before it could be op
- brew-cask: refusing operation through untrusted directory {}
AI-assisted analysis of jdx/mise@afd2eddd3a (2026-09-09).
Data as JSON: /api/errors/5e345174502798f9.
Report an issue: GitHub.