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

  1. Run the test to confirm ensure_trusted_appdir succeeds when it must fail
  2. 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)
  3. Ensure canonicalization of the base path doesn't mask ancestor permission checks
  4. 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

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


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