diff --git a/workplans/SAND-WP-0013-bwrap-extension.md b/workplans/SAND-WP-0013-bwrap-extension.md new file mode 100644 index 0000000..b235dab --- /dev/null +++ b/workplans/SAND-WP-0013-bwrap-extension.md @@ -0,0 +1,104 @@ +--- +id: SAND-WP-0013 +title: "Bubblewrap (bwrap) local-exec extension" +status: proposed +--- + +Add the first local, same-host, kernel-namespace-only sandbox extension. +Every current self-hosted extension (`ext.compose-ssh`, `ext.vm-packer`) +requires an SSH hop to a remote host plus a container runtime or VM. +`bwrap` (already installed on the workstation, unused) fills the gap: a +fast, no-remote-host, no-container-runtime backend, and the first +extension able to *actually* enforce the `network.default: deny` every +profile already declares but only documents today (`docs/security.md` +admits enforcement "varies by extension"). + +## Task: Extend the isolation-level model + +`IsolationSpec.level` in `src/sandboxer/models.py` is a closed +`Literal["container", "microvm", "policy"]`. Add a value for +namespace/process-level isolation (e.g. `"process"`) and update any +validation/display logic that switches on it. + +```task +id: SAND-WP-0013-T01 +status: todo +priority: high +``` + +## Task: Implement `BwrapExtension` + +New `src/sandboxer/extensions/bwrap.py` implementing the +`SandboxExtension` ABC (`provision`/`wait_ready`/`teardown`): +`provision()` creates a per-sandbox workspace dir, spawns the target +under `bwrap` with new user/mount/pid/net namespaces, a tmpfs root, bind +mounts scoped to the workspace, and `--unshare-net` (or a loopback-only +net namespace) so declared network-deny is real; handle = +`{sandbox_id, host: "localhost", pid, workspace_dir}`. `wait_ready()` +checks the process is alive and the workspace exists. `teardown()` kills +the process group and removes the workspace. No snapshot support +initially (`supports_snapshots()` stays `False`). + +```task +id: SAND-WP-0013-T02 +status: todo +priority: high +``` + +## Task: Register the extension and a profile + +`extensions/ext.bwrap.yaml` (handler path, `capabilities.isolation_levels` +including the new value, `pricing_model: self-hosted`). New +`profiles/profile.bwrap-local.yaml` bound to `ext.bwrap`, `network. +default: deny` actually meaningful this time, suitable for CI/local dev. + +```task +id: SAND-WP-0013-T03 +status: todo +priority: medium +``` + +## Task: Resolve local reachability vs. the SSH-based consumer contract + +`docs/integrations/glas-harness.md` documents glas-harness consuming +sandboxes via `reachability.ssh` + `remote_dir`. A same-host bwrap +sandbox has no SSH hop. Decide and document: a new local reachability +descriptor (e.g. `reachability.local` with a direct workspace path + +PID), vs. requiring glas-harness to gain a direct-subprocess-exec +consumption mode. Update `docs/integrations/glas-harness.md` and +`reachability/enrich.py` accordingly. Coordinate with glas-harness +GLAS-WP-0001 (harness contract) so both sides agree before either is +built against the other. + +```task +id: SAND-WP-0013-T04 +status: todo +priority: high +``` + +## Task: Extend manager's handle whitelist + +`core/manager.py::_handle_from_status` reconstructs a fixed set of +fields from stored inputs for teardown/snapshot calls. Add bwrap's +handle fields (`pid`, `workspace_dir`) to that whitelist so +teardown/restart survives a `sandboxer` CLI/process restart. + +```task +id: SAND-WP-0013-T05 +status: todo +priority: medium +``` + +## Task: Tests + +Mirror the existing extension test pattern (mock the subprocess/bwrap +invocation the way `tests/` mocks `SSHConfig.run` for compose-ssh); +cover provision/wait_ready/teardown, the new isolation-level value, and +extension_base conformance (`SandboxExtension` subclass checks already +exercised generically in `test_extension_base.py`). + +```task +id: SAND-WP-0013-T06 +status: todo +priority: medium +```