fe6851e6c4
* feat(sandbox): scan write_paths for dotfiles too The dotfile / escaping-symlink masker walked cwd and every read_paths root but skipped write_paths, so a writable directory granted outside cwd could still leak — and let the helper overwrite — top-level secrets like .env / .aws / .ssh. Fold read_paths and write_paths into one deduplicated, ancestor-first set via a new merge_scan_roots helper so every granted root is masked, and a path granted by more than one lever (or nested under another grant) is walked once instead of once per lever. The dedup resolves each root a single time and skips nested roots with a lexicographic cover scan, so the big-grant profile-size guard stays fast. Signed-off-by: Thomas Garnier <6202935+mxatone@users.noreply.github.com> * fix(sandbox): only drop nested grants when scanning recursively Review caught that merge_scan_roots dropped a granted root whenever another grant was its ancestor — but that subsumption only holds when the walk is recursive. cwd_hidden_scan_recursive defaults to False, where each walk masks only a root's immediate children, so dropping a nested grant (e.g. write_paths: [/a/deep/nested] under read_paths: [/a]) left its top-level dotfiles visible and writable — reintroducing the exact leak this branch closes, and regressing the prior per-read-root behavior. Thread the recursive flag into merge_scan_roots: keep the cwd drop (unchanged, pre-existing), but only collapse a grant into a kept ancestor when recursive=True; in top-level-only mode keep every distinct grant and drop only exact duplicates. Walk the full ancestor chain (not just the last kept root) so an interleaving sibling name cannot hide a real ancestor and leave a redundant walk. Adds regression tests in both backends for the non-recursive nested grant, plus merge_scan_roots unit coverage for both modes. Signed-off-by: Thomas Garnier <6202935+mxatone@users.noreply.github.com> * fix(sandbox): exclude framework scratch roots from the write-root dotfile scan Extending the dotfile mask scan to write_paths also swept in the framework-added scratch tmpdir (folded into write_roots via with_additional_write_roots). That dir holds the sandbox's own egress relay socket `.egress.sock` — a dotfile — so the scan masked it with `--bind-try /dev/null` (bwrap) / a deny rule (seatbelt), cutting the relay endpoint and resetting every egress connection. This is what broke the inner-rest `test_egress_e2e[linux_bwrap]` cases. Track framework write roots on the policy as `mask_scan_skip_roots` and drop them (and anything nested under them) from `merge_scan_roots`. These dirs are created fresh by the framework and never hold pre-existing user secrets, so scanning them is both pointless and harmful. Genuine user-declared read/write grants are still scanned. Signed-off-by: Thomas Garnier <6202935+mxatone@users.noreply.github.com> --------- Signed-off-by: Thomas Garnier <6202935+mxatone@users.noreply.github.com>