sand-boxer/docs/extension-sdk.md
tegwick d79e3fe358 Add owner-mediated bwrap execution boundary
Assistant: codex
Assistant-Model: gpt-5.6-sol
Assistant-Session: 01a06def-6490-7033-8448-2eab2d12ed44
2026-09-04 22:12:19 +02:00

123 lines
3.9 KiB
Markdown

# Extension SDK
Author guide for sand-boxer backend adapters. Version 0.1 — SAND-WP-0005.
## Contract
Every extension implements three methods:
```text
provision(profile, inputs, host) → handle dict
wait_ready(handle) → reachability dict
teardown(handle) → cleanup report dict
```
Optional (SaaS): `estimate_cost(profile, duration) → MeterQuote`
Optional (checkpoints): `supports_snapshots()`, `snapshot(handle)`,
`restore_from_snapshot(profile, snapshot_meta, inputs, host)`
Optional (owner-mediated execution): `supports_execution()`, then
`execute(handle, command, credential_route_refs, execution_context,
timeout_seconds, max_output_bytes)`. The default implementation fails closed.
`ext.bwrap` is the reference implementation. An executing extension must
validate that its workspace belongs to the exact sandbox handle, sanitize the
child environment, use an argument vector, enforce the requested bounds, and
must not fall back to a host workspace.
### Base class
```python
from sandboxer.extensions.base import SandboxExtension
class MyExtension(SandboxExtension):
def provision(self, profile, inputs, host): ...
def wait_ready(self, handle): ...
def teardown(self, handle): ...
```
Reference implementations:
| Extension | Module | Mode |
|-----------|--------|------|
| `ext.compose-ssh` | `compose_ssh.py` | Remote compose stack + tar snapshots |
| `ext.vm-packer` | `vm_packer.py` | Attach workspace or Packer build mode |
| `ext.saas-stub` | `saas_stub.py` | Metered stub + metadata snapshots |
| `ext.e2b` | `e2b.py` | E2B cloud adapter |
| `ext.modal` | `modal.py` | Modal cloud adapter |
| `ext.bwrap` | `bwrap.py` | Local namespaces + owner-mediated execution |
## Registration
1. Add `extensions/ext.<name>.yaml`:
```yaml
id: ext.my-backend
title: My Backend
handler: sandboxer.extensions.my_backend:MyExtension
capabilities:
isolation_levels: [container]
pricing_model: self-hosted
config:
key: value
```
2. Add a profile binding in `profiles/profile.<slug>.yaml` with `extension: ext.my-backend`.
3. Register capability metadata in `registry/` when ready for reuse-surface.
Loader validates `capabilities.isolation_levels` and `capabilities.pricing_model`
at startup (`sandboxer.extensions.registry`).
## Handle and reachability
**Handle** (returned by `provision`, stored in manager): opaque dict passed to
`wait_ready` and `teardown`. Include at minimum:
- `sandbox_id`
- `host` (placement host)
- Fields your extension needs for SSH/API (e.g. `remote_dir`, `vm_target`)
**Reachability** (returned by `wait_ready`): exposed on `SandboxStatus.reachability`:
- `ssh` — SSH destination string
- `remote_dir` — workspace path on remote
- `host` — placement host
- `compose_project` — compose-ssh only
## Inputs convention
Profiles declare semantics; extensions validate required `inputs` keys:
| Extension | Required inputs | Optional |
|-----------|-----------------|----------|
| compose-ssh | `repo` | `sandbox_id` |
| vm-packer (attach) | `vm` or `ssh_target` | `repo`, `tunnel_port`, `ssh_port`, `workspace_dir` |
| vm-packer (build) | `packer_template`, `vm_name` | `mode=build`, `packer_var_*` |
Consumer attribution travels on `SandboxCreateRequest.consumer`, not extension inputs.
## Testing
Mock SSH/subprocess in unit tests. See `tests/test_compose_ssh.py`, `tests/test_vm_packer.py`.
Pattern:
```python
with patch.object(SSHConfig, "run", return_value=(0, "ready")):
ext = VMPackerExtension()
handle = ext.provision(profile, {"vm": "haskell-build"}, "localhost")
```
## Metered extensions (SAND-WP-0006)
Implement `estimate_cost` and `meter_actual` on `SandboxExtension`. Register with
`pricing_model: metered`. See `docs/payments.md` and `ext.saas-stub`.
## Deferred
| Feature | Workplan |
|---------|----------|
| Packer build orchestration from `create` | Future WP |
| Daytona OSS cloud adapter | Future WP |
| fin-hub billing export | Future |
| Cross-host snapshot transfer | Future |