astrid-runtime/astrid · error
process path {} overlaps a copy-on-write mask
Error message
process path {} overlaps a copy-on-write mask What it means
After computing the process paths, wrap_with_process_paths rejects any granted read or write path that overlaps a copy-on-write mask. Overlap would let the child read or write through a path the mask is supposed to deny, defeating the CoW guarantee, so the call fails with PermissionDenied before spawning.
Source
Thrown at crates/astrid-workspace/src/sandbox/mod.rs:307
for masked in extra_masks {
let _ = validate_sandbox_str(masked, "workspace CoW mask")?;
if !masked.exists() {
return Err(io::Error::new(
io::ErrorKind::NotFound,
format!(
"workspace CoW mask path does not exist: {} — refusing to spawn \
a child without the intended copy-on-write deny",
masked.display()
),
));
}
}
for granted in extra_read_paths.iter().chain(extra_write_paths) {
if extra_masks
.iter()
.any(|masked| paths_overlap(granted, masked))
{
return Err(io::Error::new(
io::ErrorKind::PermissionDenied,
format!(
"process path {} overlaps a copy-on-write mask",
granted.display()
),
));
}
}
#[cfg(target_os = "linux")]
{
// Bubblewrap implementation - paths are passed as separate argv entries (no injection).
// The process can only read the root OS, but can only write to the worktree and /tmp.
let mut bwrap = Command::new("bwrap");
if clear_env {
bwrap.env_clear();
}
bwrapView on GitHub (pinned to affd8760f4)
Solutions
- Remove or narrow the granted path so it no longer overlaps the mask (grant a sibling/subpath instead)
- Remove the conflicting mask if the path is legitimately meant to be writable
- Normalize both paths (canonicalize) and compare to spot overlap from symlinks or relative components
- Split the operation: run the masked child and the granted child as separate spawns
Example fix
// before
write_paths.push(PathBuf::from("/workspace")); // overlaps mask /workspace/secrets
// after
write_paths.push(PathBuf::from("/workspace/public")); Defensive patterns
Strategy: validation
Validate before calling
fn overlaps(a: &Path, b: &Path) -> bool { a.starts_with(b) || b.starts_with(a) }
assert!(grants.iter().all(|g| !masks.iter().any(|m| overlaps(g, m))),
"grant overlaps a CoW mask"); Type guard
fn grant_safe(grant: &Path, masks: &[PathBuf]) -> bool {
!masks.iter().any(|m| paths_overlap(grant, m))
} Try / catch
match wrap_with_process_paths(...) {
Err(e) if e.kind() == io::ErrorKind::PermissionDenied
&& e.to_string().contains("overlaps a copy-on-write mask") =>
eprintln!("narrow grant: {e}"),
other => other?,
} Prevention
- Canonicalize both grants and masks before comparing
- Keep grant and mask path lists in one reviewed config section
- Add a preflight check that reports the exact overlapping pair
When it happens
Trigger: Calling wrap_with_process_paths where an entry in extra_read_paths or extra_write_paths shares a path prefix (per paths_overlap) with any entry in extra_masks.
Common situations: Granting the workspace root as writable while also masking a subdirectory of it; copy-pasted path lists where a grant and a mask point at the same directory; symlink or relative-path aliases that resolve to the same target.
Understand the failure class
Background: Permission denied / not authorized / 403 Forbidden: access-control rejections when the caller lacks the required role, grant, or ownership — this error's family across 18 libraries.
Related errors
- process path {} overlaps a sensitive runtime path
- DNS resolved to an unauthorized private or local IP address
- sandbox {label} contains forbidden characters (double-quote,
- workspace CoW mask path does not exist: {} — refusing to spa
- manifest exceeds its installed capability approval: {details
AI-assisted analysis of astrid-runtime/astrid@affd8760f4 (2026-09-09).
Data as JSON: /api/errors/f4e1617495b222f0.
Report an issue: GitHub.