--- id: SAND-WP-0013 type: workplan title: "Bubblewrap (bwrap) local-exec extension" status: finished state_hub_workstream_id: "34e39c38-ecb8-5be6-bdf8-7d110fb0c4aa" --- 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: done priority: high state_hub_task_id: "91f50ae4-7080-5475-bb85-614c8fe90860" ``` ## 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: done priority: high state_hub_task_id: "8b41c12b-e093-5f57-8c25-17d8d0c96082" ``` ## 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: done priority: medium state_hub_task_id: "cf29db06-c427-582e-ba17-c5fb53867a1f" ``` ## 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: done priority: high state_hub_task_id: "378118e7-0d19-59c7-b998-5b26ed442b3b" ``` ## 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: done priority: medium state_hub_task_id: "b6638a8e-8577-538a-ac5b-3b56775bd48f" ``` ## 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: done priority: medium state_hub_task_id: "f9805c5a-2bbe-5fc9-b084-b41e0683460d" ```