{"record":{"id":"23a62992c0b237da","repo":"astrid-runtime/astrid","slug":"workspace-cow-mask-path-does-not-exist-refus","errorCode":null,"errorMessage":"workspace CoW mask path does not exist: {} — refusing to spawn a child without the intended copy-on-write deny","messagePattern":"workspace CoW mask path does not exist: (.+?) — refusing to spawn a child without the intended copy-on-write deny","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"crates/astrid-workspace/src/sandbox/mod.rs","lineNumber":292,"sourceCode":"            if !path.exists() {\n                return Err(io::Error::new(\n                    io::ErrorKind::NotFound,\n                    format!(\"process write path does not exist: {}\", path.display()),\n                ));\n            }\n        }\n\n        // Every caller-supplied mask names copy-on-write bookkeeping the child\n        // must not reach (the overlayfs upper/work, or the APFS pristine). Each is\n        // validated exactly like the worktree and injection paths — absolute,\n        // UTF-8, SBPL-safe — because on macOS it is interpolated into the Seatbelt\n        // profile; then it must EXIST, since a path that does not exist is a wiring\n        // bug, not a no-op (silently skipping it leaves the child un-denied). The\n        // deny is security-critical, so either failure fails the spawn closed.\n        for masked in extra_masks {\n            let _ = validate_sandbox_str(masked, \"workspace CoW mask\")?;\n            if !masked.exists() {\n                return Err(io::Error::new(\n                    io::ErrorKind::NotFound,\n                    format!(\n                        \"workspace CoW mask path does not exist: {} — refusing to spawn \\\n                         a child without the intended copy-on-write deny\",\n                        masked.display()\n                    ),\n                ));\n            }\n        }\n        for granted in extra_read_paths.iter().chain(extra_write_paths) {\n            if extra_masks\n                .iter()\n                .any(|masked| paths_overlap(granted, masked))\n            {\n                return Err(io::Error::new(\n                    io::ErrorKind::PermissionDenied,\n                    format!(\n                        \"process path {} overlaps a copy-on-write mask\",","sourceCodeStart":274,"sourceCodeEnd":310,"githubUrl":"https://github.com/astrid-runtime/astrid/blob/affd8760f44190dbdfbec23403f4c4b642c33112/crates/astrid-workspace/src/sandbox/mod.rs#L274-L310","documentation":"wrap_with_process_paths validates every copy-on-write mask path before spawning a sandboxed child. A mask that does not exist means the sandbox wiring is buggy, not that the mask can be skipped: silently dropping it would leave the child un-denied for a security-critical path. The spawn therefore fails closed with NotFound.","triggerScenarios":"Calling wrap_with_process_paths with an entry in extra_masks that points at a nonexistent path (typo, deleted file, path created later, wrong profile root).","commonSituations":"Config referencing a CoW mask path that was renamed or removed; running before an initialization step that creates the mask target; mounting the sandbox in a fresh container where the mask path was never provisioned.","solutions":["Verify the mask path exists with ls/stat and correct the spelling or value in the sandbox config","Create the masked path before spawn (e.g. touch/mkdir) if it is meant to be provisioned at startup","Pass only paths produced by the workspace's own mask-resolution helpers instead of hand-built strings","If the mask is genuinely optional, remove it from extra_masks rather than relying on silent skipping"],"exampleFix":"// before\nmasks.push(PathBuf::from(\"/opt/app/deny-bin\"));\n// after\nlet mask = PathBuf::from(\"/opt/app/deny-bin\");\nassert!(mask.exists(), \"CoW mask must exist before spawn\");\nmasks.push(mask);","handlingStrategy":"validation","validationCode":"let masked = Path::new(\"/opt/app/deny-bin\");\nif !masked.exists() {\n    return Err(io::Error::new(io::ErrorKind::NotFound,\n        format!(\"CoW mask missing: {}\", masked.display())));\n}","typeGuard":"fn mask_exists(p: &Path) -> bool { p.exists() }","tryCatchPattern":"match wrap_with_process_paths(...) {\n    Err(e) if e.kind() == io::ErrorKind::NotFound =>\n        eprintln!(\"fix mask path: {e}\"),\n    other => other?,\n}","preventionTips":["Resolve mask paths through the workspace's own helpers, never hand-built strings","Canonicalize configured paths once at load time and fail fast on missing entries","Add an integration test that spawns with the production mask set"],"tags":["sandbox","security","filesystem","path-validation"],"backgroundTag":"file-not-found","analyzedSha":"affd8760f44190dbdfbec23403f4c4b642c33112","analyzedAt":"2026-09-09T21:28:12.402Z","contentChangedAt":"2026-09-09T21:28:12.402Z","schemaVersion":2},"datasetVersion":"2026-09-14T05:17:10.506Z"}