sand-boxer/docs/extension-sdk.md
tegwick b6655d8859
All checks were successful
CI Smoke / host-smoke (push) Successful in 0s
CI Smoke / container-smoke (push) Successful in 2s
Add bounded stdin to owner execution
Assistant: codex
Assistant-Model: gpt-5.6-sol
Assistant-Session: 01a06def-6490-7033-8448-2eab2d12ed44
2026-09-04 22:30:11 +02:00

125 lines
4 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, stdin_text)`. 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.
Bounded stdin is content-bearing and must never be included in lifecycle logs or
execution evidence.
### 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 |