fix(seatbelt): narrow macOS sandbox write grants under built-in profile#781
Conversation
The static seatbelt file-write policy granted `(subpath
"/private/var/folders")` — the entire per-user temp/cache tree (browser
caches, app sqlite, token caches, …). Under the built-in profile this
grant is vestigial:
- `vmm/controller/spawn.rs::configure_env` already redirects the shim's
TMPDIR/TMP/TEMP to the box-scoped `tmp_dir`
(`~/.boxlite/boxes/{id}/tmp`) whenever
`jailer_enabled && sandbox_profile.is_none()`, so libkrun's transient
`krun-empty-root-*` and anything else honoring TMPDIR no longer lands
in `/private/var/folders`.
- Since boxlite-ai#742 every box socket (gvproxy `net.sock`, libkrun's derived
`net.sock-krun.sock`, `box.sock`, `ready.sock`) is bound through
`/tmp/bl-{uid}/{box_id}/`, added dynamically via
`BoxSockets::policy_paths()` — also not under `/private/var/folders`.
Network-side narrowing (`(allow network-inbound)` is broader than the
forwarded EXPOSE ports require) is deferred: the port list is runtime
data in `NetworkBackendConfig.final_mappings` that doesn't fit
`sandbox-exec -D` string substitution, needs dynamic policy
construction, careful disambiguation of `network-bind` vs
`network-inbound` for the gvproxy NAT return path, and real EXPOSE e2e
before tightening. The TODO comment in seatbelt_network_policy.sbpl
records those constraints.
Test plan:
- Two-side verification of the new regression test
`test_file_write_policy_excludes_per_user_temp_tree`:
1. Temporarily re-added `(subpath "/private/var/folders")` to the
sbpl → new test FAILED at the grant-form check; confirms the
test catches the regression, not a tautology.
2. Restored final sbpl → 27/27 seatbelt unit tests pass on macOS
arm64, including 4 runtime tests that spawn real `sandbox-exec`
invocations (allow-write-to-writable-path,
deny-write-outside-writable-path, allow-exec-from-tmp-dynamic-path,
deny-read-outside-allowlist).
- Live VM e2e on macOS arm64:
- `boxlite run --rm alpine:latest sh -c '...'` → guest booted
(Linux 6.12.76 aarch64), command executed, output returned
(`uid=0(root)`), auto-stopped. No EPERM / sandbox denial.
- `boxlite run -d -p HOST:8080 alpine:latest sh -c 'nc -l -p 8080'`
→ `curl http://127.0.0.1:HOST/` returned the guest payload via
gvproxy NAT, `boxlite stop` cleaned up.
|
No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info⚙️ Run configurationConfiguration used: defaults Review profile: CHILL Plan: Pro Plus Run ID: 📒 Files selected for processing (3)
📝 WalkthroughWalkthroughRemoves ChangesSeatbelt sandbox policy hardening
Estimated code review effort🎯 2 (Simple) | ⏱️ ~10 minutes Poem
🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches🧪 Generate unit tests (beta)
Comment |
Drops the static seatbelt
(subpath "/private/var/folders")write grant under the built-in macOS profile.That grant was vestigial:
vmm/controller/spawn.rs::configure_envalready redirects the shim'sTMPDIRto a box-scoped path wheneverjailer_enabled && sandbox_profile.is_none(), and since #742 every box socket binds through/tmp/bl-{uid}/{box_id}/(added dynamically viaBoxSockets::policy_paths()). With both already in place, no per-user temp tree write was actually needed.Updated TODO in
seatbelt_network_policy.sbpldocuments why narrowing(allow network-inbound)is non-trivial (port list is runtime data, doesn't fitsandbox-exec -D;network-bindvsnetwork-inbounddistinction matters for the gvproxy NAT return path; needs real EXPOSE e2e). Out of scope for this PR.Test plan (macOS arm64)
test_file_write_policy_excludes_per_user_temp_tree:(subpath "/private/var/folders")→ test FAILS at the grant-form checktest_seatbelt_runtime_*that spawn realsandbox-execboxlite run --rm alpine:latest sh -c '...'→ guest boots (Linux 6.12.76 aarch64), command runs, output returned (uid=0(root)), auto-stopped. No EPERM / sandbox denial.boxlite run -d -p HOST:8080 alpine:latest sh -c 'nc -l -p 8080'→ hostcurl 127.0.0.1:HOST/returned guest payload via gvproxy NAT;boxlite stopcleaned up.Summary by CodeRabbit
Bug Fixes
Tests