{"record":{"id":"9113d348b53a1e14","repo":"astrid-runtime/astrid","slug":"process-path-overlaps-a-sensitive-runtime-path","errorCode":null,"errorMessage":"process path {} overlaps a sensitive runtime path","messagePattern":"process path (.+?) overlaps a sensitive runtime path","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"crates/astrid-workspace/src/sandbox/mod.rs","lineNumber":356,"sourceCode":"                bwrap.arg(\"--ro-bind\").arg(&inj.source).arg(&inj.target);\n            }\n\n            // #856 read-hole fix: the `--ro-bind / /` above mounts the entire\n            // host filesystem read-only, which exposed Astrid's secret/key/state\n            // dirs and the operator's home credential stores to the spawned\n            // process. Shadow each masked path so the agent reads nothing (see\n            // `push_mask_arg`). Placed AFTER the root ro-bind (so it overlays)\n            // and the worktree/injection binds; `run/` (socket+token) and `etc/`\n            // stay reachable for daemon access. Fail-secure: refuse the spawn if\n            // the home is unresolvable.\n            let built_in_masks = Self::masked_paths()?;\n            let home_root = astrid_core::dirs::AstridHome::resolve()?.home_dir();\n            for granted in extra_read_paths.iter().chain(extra_write_paths) {\n                if built_in_masks.iter().any(|masked| {\n                    paths_overlap(granted, masked)\n                        && !(masked == &home_root && granted.starts_with(masked))\n                }) {\n                    return Err(io::Error::new(\n                        io::ErrorKind::PermissionDenied,\n                        format!(\n                            \"process path {} overlaps a sensitive runtime path\",\n                            granted.display()\n                        ),\n                    ));\n                }\n            }\n            for masked in &built_in_masks {\n                Self::push_mask_arg(&mut bwrap, masked);\n            }\n\n            // Caller-supplied masks (the CoW upper/work dirs). Same mechanism,\n            // placed after the worktree/injection binds so they overlay; the\n            // CoW dirs live OUTSIDE the worktree, so no writable bind needs to\n            // punch back through. Existence is validated up front (a missing mask\n            // already failed the spawn), so every entry is masked unconditionally.\n            for masked in extra_masks {","sourceCodeStart":338,"sourceCodeEnd":374,"githubUrl":"https://github.com/astrid-runtime/astrid/blob/affd8760f44190dbdfbec23403f4c4b642c33112/crates/astrid-workspace/src/sandbox/mod.rs#L338-L374","documentation":"wrap_with_process_paths checks every granted read/write path against the built-in sensitive runtime masks (resolved from AstridHome). Grants that overlap a built-in mask are rejected with PermissionDenied — except a grant that is strictly inside the user's home root, which is explicitly allowed.","triggerScenarios":"Calling wrap_with_process_paths with extra_read_paths/extra_write_paths entries that overlap built-in masks such as the AstridHome runtime directories (and are not home_root-relative), or that equal the home root without starting beneath it as a proper subpath.","commonSituations":"Pointing a process grant at the AstridHome config/state directory directly; granting a parent directory (e.g. $HOME or /etc) that envelopes a sensitive runtime path; after relocating AstridHome so an old grant now collides with the new runtime location.","solutions":["Move the grant to a subpath under home_root so the allowed exception applies","Narrow the granted path to exclude the sensitive runtime directory","Canonicalize the grant and re-check overlap; a trailing-slash or .. component can make it envelope the mask","Relocate the workspace/process data outside the sensitive runtime tree"],"exampleFix":"// before\nread_paths.push(home.join(\".astrid\")); // is the sensitive runtime root itself\n// after\nread_paths.push(home.join(\"projects\")); // strictly beneath home_root","handlingStrategy":"validation","validationCode":"let home = astrid_core::dirs::AstridHome::resolve()?.home_dir();\nif !granted.starts_with(&home) {\n    panic!(\"grant {} must live under AstridHome\", granted.display());\n}","typeGuard":"fn under_home(p: &Path, home: &Path) -> bool {\n    p.starts_with(home)\n}","tryCatchPattern":"match wrap_with_process_paths(...) {\n    Err(e) if e.to_string().contains(\"sensitive runtime path\") =>\n        eprintln!(\"move grant under home or off the runtime path: {e}\"),\n    other => other?,\n}","preventionTips":["Always derive process grants from the resolved AstridHome root, not absolute guesses","Re-validate grants after any AstridHome relocation","Document which runtime directories are off-limits to grants"],"tags":["sandbox","security","sensitive-paths","permission"],"backgroundTag":"permission-denied","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"}