@namzu/sandbox 1.1.0 → 2.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (91) hide show
  1. package/CHANGELOG.md +277 -0
  2. package/README.md +205 -105
  3. package/dist/backends/aci-standby-pool/__tests__/unenforceable-controls.test.d.ts +2 -0
  4. package/dist/backends/aci-standby-pool/__tests__/unenforceable-controls.test.d.ts.map +1 -0
  5. package/dist/backends/aci-standby-pool/__tests__/unenforceable-controls.test.js +61 -0
  6. package/dist/backends/aci-standby-pool/__tests__/unenforceable-controls.test.js.map +1 -0
  7. package/dist/backends/aci-standby-pool/index.d.ts +2 -1
  8. package/dist/backends/aci-standby-pool/index.d.ts.map +1 -1
  9. package/dist/backends/aci-standby-pool/index.js +36 -1
  10. package/dist/backends/aci-standby-pool/index.js.map +1 -1
  11. package/dist/backends/docker/__tests__/hardening.test.d.ts +2 -0
  12. package/dist/backends/docker/__tests__/hardening.test.d.ts.map +1 -0
  13. package/dist/backends/docker/__tests__/hardening.test.js +32 -0
  14. package/dist/backends/docker/__tests__/hardening.test.js.map +1 -0
  15. package/dist/backends/docker/__tests__/leaf-permissions.smoke.test.d.ts +1 -1
  16. package/dist/backends/docker/__tests__/leaf-permissions.smoke.test.js +1 -1
  17. package/dist/backends/docker/index.d.ts +47 -3
  18. package/dist/backends/docker/index.d.ts.map +1 -1
  19. package/dist/backends/docker/index.js +144 -5
  20. package/dist/backends/docker/index.js.map +1 -1
  21. package/dist/backends/firecracker/__tests__/agent-timeout-clamp.test.d.ts +16 -0
  22. package/dist/backends/firecracker/__tests__/agent-timeout-clamp.test.d.ts.map +1 -0
  23. package/dist/backends/firecracker/__tests__/agent-timeout-clamp.test.js +37 -0
  24. package/dist/backends/firecracker/__tests__/agent-timeout-clamp.test.js.map +1 -0
  25. package/dist/backends/firecracker/__tests__/backend.test.js +11 -3
  26. package/dist/backends/firecracker/__tests__/backend.test.js.map +1 -1
  27. package/dist/backends/firecracker/__tests__/control-plane-mtls.test.js +10 -2
  28. package/dist/backends/firecracker/__tests__/control-plane-mtls.test.js.map +1 -1
  29. package/dist/backends/firecracker/__tests__/egress-policy.test.d.ts +2 -0
  30. package/dist/backends/firecracker/__tests__/egress-policy.test.d.ts.map +1 -0
  31. package/dist/backends/firecracker/__tests__/egress-policy.test.js +67 -0
  32. package/dist/backends/firecracker/__tests__/egress-policy.test.js.map +1 -0
  33. package/dist/backends/firecracker/__tests__/fixtures/ipc-path.d.ts +21 -0
  34. package/dist/backends/firecracker/__tests__/fixtures/ipc-path.d.ts.map +1 -0
  35. package/dist/backends/firecracker/__tests__/fixtures/ipc-path.js +30 -0
  36. package/dist/backends/firecracker/__tests__/fixtures/ipc-path.js.map +1 -0
  37. package/dist/backends/firecracker/__tests__/protocol.test.js +7 -17
  38. package/dist/backends/firecracker/__tests__/protocol.test.js.map +1 -1
  39. package/dist/backends/firecracker/__tests__/transport.test.js +11 -3
  40. package/dist/backends/firecracker/__tests__/transport.test.js.map +1 -1
  41. package/dist/backends/firecracker/index.d.ts +20 -1
  42. package/dist/backends/firecracker/index.d.ts.map +1 -1
  43. package/dist/backends/firecracker/index.js +70 -15
  44. package/dist/backends/firecracker/index.js.map +1 -1
  45. package/dist/egress/__tests__/allowlist.test.d.ts +2 -0
  46. package/dist/egress/__tests__/allowlist.test.d.ts.map +1 -0
  47. package/dist/egress/__tests__/allowlist.test.js +85 -0
  48. package/dist/egress/__tests__/allowlist.test.js.map +1 -0
  49. package/dist/egress/__tests__/proxy.test.d.ts +2 -0
  50. package/dist/egress/__tests__/proxy.test.d.ts.map +1 -0
  51. package/dist/egress/__tests__/proxy.test.js +177 -0
  52. package/dist/egress/__tests__/proxy.test.js.map +1 -0
  53. package/dist/egress/allowlist.d.ts +40 -0
  54. package/dist/egress/allowlist.d.ts.map +1 -0
  55. package/dist/egress/allowlist.js +81 -0
  56. package/dist/egress/allowlist.js.map +1 -0
  57. package/dist/egress/index.d.ts +4 -0
  58. package/dist/egress/index.d.ts.map +1 -0
  59. package/dist/egress/index.js +3 -0
  60. package/dist/egress/index.js.map +1 -0
  61. package/dist/egress/proxy.d.ts +90 -0
  62. package/dist/egress/proxy.d.ts.map +1 -0
  63. package/dist/egress/proxy.js +194 -0
  64. package/dist/egress/proxy.js.map +1 -0
  65. package/dist/index.d.ts +101 -190
  66. package/dist/index.d.ts.map +1 -1
  67. package/dist/index.js +62 -80
  68. package/dist/index.js.map +1 -1
  69. package/dist/index.test.js +18 -39
  70. package/dist/index.test.js.map +1 -1
  71. package/package.json +5 -4
  72. package/src/backends/aci-standby-pool/__tests__/unenforceable-controls.test.ts +69 -0
  73. package/src/backends/aci-standby-pool/index.ts +42 -1
  74. package/src/backends/docker/__tests__/hardening.test.ts +43 -0
  75. package/src/backends/docker/__tests__/leaf-permissions.smoke.test.ts +1 -1
  76. package/src/backends/docker/index.ts +210 -12
  77. package/src/backends/firecracker/__tests__/agent-timeout-clamp.test.ts +48 -0
  78. package/src/backends/firecracker/__tests__/backend.test.ts +76 -65
  79. package/src/backends/firecracker/__tests__/control-plane-mtls.test.ts +10 -2
  80. package/src/backends/firecracker/__tests__/egress-policy.test.ts +91 -0
  81. package/src/backends/firecracker/__tests__/fixtures/ipc-path.ts +31 -0
  82. package/src/backends/firecracker/__tests__/protocol.test.ts +8 -23
  83. package/src/backends/firecracker/__tests__/transport.test.ts +11 -3
  84. package/src/backends/firecracker/index.ts +76 -13
  85. package/src/egress/__tests__/allowlist.test.ts +103 -0
  86. package/src/egress/__tests__/proxy.test.ts +212 -0
  87. package/src/egress/allowlist.ts +82 -0
  88. package/src/egress/index.ts +7 -0
  89. package/src/egress/proxy.ts +294 -0
  90. package/src/index.test.ts +19 -41
  91. package/src/index.ts +170 -259
package/src/index.ts CHANGED
@@ -1,69 +1,37 @@
1
1
  /**
2
- * @namzu/sandbox — pluggable sandbox provider for @namzu/sdk.
3
- *
4
- * The SDK already declares a `SandboxProvider` shape in
5
- * `@namzu/sdk` (`packages/sdk/src/types/sandbox/index.ts`). This
6
- * package implements that shape with concrete BACKENDS picked at
7
- * construction time. The set is aligned with the 2026 industrial
8
- * standard for AI-agent code-execution sandboxes:
9
- *
10
- * • `docker` — plain OCI container per task, seccomp default
11
- * profile, tmpfs workdir, no-network-by-default. The universal
12
- * fallback every namzu host gets locally with `docker compose`
13
- * and on every Linux replica in any cloud. What Northflank /
14
- * Railway / Render / Compass-platform / GitHub Actions runners
15
- * actually ship for code execution. Trust boundary: namespaces.
16
- *
17
- * • `e2b` — adapter for E2B's managed Firecracker microVM service
18
- * (`e2b.dev`). Sub-second cold-start via snapshot/restore, full
19
- * kernel-level trust boundary. The SaaS-friendly path to real
20
- * Firecracker isolation without running our own scheduler.
21
- *
22
- * • `fly-machines` — adapter for Fly Machines (`fly.io/docs/machines`).
23
- * Also Firecracker microVMs; closer to bare-metal control than
24
- * E2B, useful when the workload is more "arbitrary tool calls"
25
- * than "Python REPL".
26
- *
27
- * • `firecracker` — self-hosted `firecracker-containerd` on bare
28
- * metal (or KVM-enabled cloud instance). For hosts that need
29
- * Firecracker isolation AND insist on running the scheduler
30
- * themselves. Tier 3 isolation, highest operational cost.
31
- *
32
- * • `gvisor` — adapter for `runsc` runtime (Google's userspace
33
- * kernel). What OpenAI Code Interpreter and Modal Labs ship.
34
- * Trusted-tenant tier with near-zero cold-start; runs on
35
- * commodity Linux without nested virt. Locally via Linux Docker
36
- * runtime; not available on macOS Docker Desktop.
37
- *
38
- * • `passthrough` — no isolation, runs commands directly. For
39
- * tests and trusted environments only. Off by default; opt-in.
40
- *
41
- * **What we deliberately do NOT build** is yet-another Firecracker
42
- * scheduler — that is E2B's and Fly's entire product, and writing
43
- * our own is a years-long detour. We adapt to theirs.
44
- *
45
- * **Cloud portability:** the backend interface is cloud-agnostic.
46
- * `docker` works on every cloud; `e2b` and `fly-machines` are
47
- * managed services not tied to any cloud; `firecracker` and
48
- * `gvisor` need infrastructure the host chooses (GKE Sandbox, AWS
49
- * Fargate, self-hosted KVM, etc.). Picking a stronger backend may
50
- * imply moving cloud — that's the host's call, not the SDK's.
51
- *
52
- * **Local dev story:** Phase 1 (`docker`) runs everywhere. Phase 2
53
- * (`e2b` / `fly-machines`) hits the managed service from a dev
54
- * laptop with no infra setup. Phase 3 (`firecracker` / `gvisor`)
55
- * needs Lima/Colima on macOS or native KVM on Linux — only the
56
- * adversarial-multi-tenant prod path needs that and it's clearly
57
- * documented as such.
58
- *
59
- * This file is the public surface. Concrete backend implementations
60
- * land under `./backends/<kind>/` in subsequent commits — the
61
- * `SandboxBackend` interface here is the contract they implement.
62
- *
63
- * Refs: `e2b.dev/docs/sandbox`, `fly.io/docs/machines`,
64
- * `firecracker-microvm.github.io`, `gvisor.dev/docs`,
65
- * `cloud.google.com/kubernetes-engine/docs/concepts/sandbox-pods`,
66
- * `aws.amazon.com/blogs/aws/firecracker-lightweight-virtualization-for-serverless-computing`.
2
+ * @namzu/sandbox — pluggable containment for @namzu/sdk.
3
+ *
4
+ * The SDK declares the `SandboxProvider` shape
5
+ * (`packages/sdk/src/types/sandbox/index.ts`); this package implements
6
+ * it with concrete BACKENDS chosen at construction time. A backend is
7
+ * named for the mechanism it drives, because that is what it has to
8
+ * speak on the wire — never for a system whose ideas it borrowed.
9
+ *
10
+ * Two tiers, each a trust boundary:
11
+ *
12
+ * • `container` — one OCI container per task, seccomp on, tmpfs
13
+ * workdir, no network unless asked. The same path on a laptop and
14
+ * on a Linux replica anywhere. The tier for trusted prompts and
15
+ * contained workloads. Boundary: kernel namespaces, or a
16
+ * userspace-kernel runtime where one is installed.
17
+ *
18
+ * • `microvm` — one hardware-virtualized guest per task. The boundary
19
+ * to reach for when the prompt itself is adversarial, at the cost
20
+ * of running or renting the machinery that starts them.
21
+ *
22
+ * Every shape in {@link SandboxBackendConfig} has a backend behind it,
23
+ * which used not to be true: a `process` tier, a `passthrough` tier and
24
+ * two adapters to third-party schedulers were declared here and never
25
+ * written, so four of the shapes this package offered could only ever
26
+ * type-check and then throw. They are gone rather than pending.
27
+ * Confining an agent to the operator's own host is the SDK's local
28
+ * sandbox provider, which is implemented; a host that wants no
29
+ * confinement configures no sandbox.
30
+ *
31
+ * namzu does not build its own microVM scheduler. That is a years-long
32
+ * detour from an agent kernel, and the boundary a guest gives is the
33
+ * same whoever started it — so the microvm tier is an interface to a
34
+ * scheduler, not a scheduler.
67
35
  */
68
36
 
69
37
  import type {
@@ -120,35 +88,27 @@ export {
120
88
  // ---------------------------------------------------------------------------
121
89
 
122
90
  /**
123
- * Top-level sandbox tier. Each tier is a use-case bucket:
124
- *
125
- * - `process` — the agent runs on the developer's own host;
126
- * the sandbox keeps it from reading `~/.ssh` or running
127
- * `rm -rf ~`. Single-user; no multi-tenancy. What Anthropic
128
- * ships with Claude Code via `@anthropic-ai/sandbox-runtime`.
129
- *
130
- * - `container` — the agent runs inside an OCI container per
131
- * task. Same code path locally (`docker compose`) and on
132
- * Linux replicas in any cloud. The default for "trusted
133
- * prompts, contained workloads" — Northflank, Railway,
134
- * Render, Compass-platform, GitHub Actions runners all
135
- * ship this tier.
136
- *
137
- * - `microvm` — the agent runs inside a Firecracker microVM
138
- * per task. Hardware-virtualization trust boundary; the
139
- * industry standard for adversarial multi-tenancy
140
- * (AWS Lambda/Fargate, Fly Machines, Replit, E2B, Daytona
141
- * all converged here). Sub-second cold-start via
142
- * snapshot/restore.
143
- *
144
- * - `passthrough` — no isolation; for tests and explicitly
145
- * trusted environments.
91
+ * Top-level sandbox tier, and the trust boundary it buys:
92
+ *
93
+ * - `container` — one OCI container per task. Namespaces. The
94
+ * default for trusted prompts and contained workloads, and the
95
+ * same code path on a laptop and on a Linux replica anywhere.
96
+ *
97
+ * - `microvm` — one hardware-virtualized guest per task. The
98
+ * boundary to reach for when the prompt itself is adversarial.
99
+ *
100
+ * Two tiers, not four. A `process` tier and a `passthrough` tier were
101
+ * declared here and never built: every construction threw, so the
102
+ * only thing they offered a caller was a shape that compiles and an
103
+ * exception at runtime. Confining the agent to the operator's own
104
+ * host is the SDK's local sandbox provider, which is implemented; a
105
+ * host that wants no confinement configures no sandbox.
146
106
  *
147
107
  * The concrete implementation inside a tier is picked via the
148
- * tier-specific config (see {@link ProcessBackendConfig},
149
- * {@link ContainerBackendConfig}, {@link MicroVMBackendConfig}).
108
+ * tier-specific config (see {@link ContainerBackendConfig},
109
+ * {@link MicroVMBackendConfig}).
150
110
  */
151
- export type SandboxTier = 'process' | 'container' | 'microvm' | 'passthrough'
111
+ export type SandboxTier = 'container' | 'microvm'
152
112
 
153
113
  /**
154
114
  * Discriminated union of sandbox backend configurations. Each
@@ -156,19 +116,17 @@ export type SandboxTier = 'process' | 'container' | 'microvm' | 'passthrough'
156
116
  * shape automatically via TS narrowing.
157
117
  */
158
118
  export type SandboxBackendConfig =
159
- | ProcessBackendConfig
160
119
  | ContainerBackendConfig
161
120
  | ACIStandbyPoolBackendConfig
162
121
  | MicroVMBackendConfig
163
- | PassthroughBackendConfig
164
122
 
165
123
  /**
166
124
  * Azure Container Instances Standby Pool backend. Container tier,
167
125
  * managed-microvm-ish: every claim is a fresh ACI container group
168
126
  * pre-warmed in an Azure-managed standby pool (`Microsoft.StandbyPool`).
169
127
  * ~1.5 s claim latency vs ~10-30 s for cold ACI spawn. Trust boundary
170
- * = Microsoft's ACI isolation host (gVisor-equivalent depending on
171
- * SKU; AMD SEV-SNP TEE when the pool is created with sku=Confidential).
128
+ * = the provider's isolation host, whose strength varies by SKU; the
129
+ * Confidential SKU adds an AMD SEV-SNP trusted execution environment.
172
130
  *
173
131
  * No host filesystem — workspace mounts ride `azureFileShare` sources
174
132
  * (the host provisions a per-task Azure Files share upstream and
@@ -211,34 +169,17 @@ export interface ACIStandbyPoolBackendConfig {
211
169
  readonly containerNamePrefix?: string
212
170
  }
213
171
 
214
- /**
215
- * `process` tier. Auto-detects the platform's native primitive
216
- * unless overridden:
217
- *
218
- * - `bubblewrap` on Linux / WSL2 (`bwrap`)
219
- * - `seatbelt` on macOS (`sandbox-exec`)
220
- *
221
- * Both are what `@anthropic-ai/sandbox-runtime` ships. Cold-start
222
- * is process spawn (~ms). Use this when the agent runs on the
223
- * end-user's developer machine — Claude Code's deployment model.
224
- */
225
- export interface ProcessBackendConfig {
226
- readonly tier: 'process'
227
- readonly engine?: 'auto' | 'bubblewrap' | 'seatbelt'
228
- }
229
-
230
172
  /**
231
173
  * `container` tier. Two runtime options:
232
174
  *
233
175
  * - `docker` (default) — plain OCI container on the host's
234
176
  * Docker daemon. No special runtime required.
235
- * - `runsc` — Google's gVisor userspace-kernel runtime. Stronger
236
- * isolation (syscall-table separation), runs on commodity
237
- * Linux without nested virt. Trusted-tenant tier; what OpenAI
238
- * Code Interpreter and Modal Labs ship. Requires the
239
- * `runsc` runtime installed on the Docker daemon (Linux only;
240
- * Docker Desktop on macOS does not support it). See
241
- * `gvisor.dev/docs/user_guide/quick_start/docker`.
177
+ * - `runsc` — a userspace-kernel runtime: the guest's syscalls
178
+ * are served by a user-space implementation rather than the
179
+ * host kernel, which is a stronger boundary than namespaces and
180
+ * runs on commodity Linux without nested virtualization.
181
+ * Requires the runtime installed on the container daemon (Linux
182
+ * only).
242
183
  *
243
184
  * `image` is the container image to spawn per task. The package
244
185
  * ships a reference Dockerfile (compass-platform pattern) with
@@ -286,119 +227,91 @@ export interface ContainerBackendConfig {
286
227
  }
287
228
 
288
229
  /**
289
- * `microvm` tier. Three concrete services, all Firecracker under
290
- * the hood:
291
- *
292
- * - `e2b` — adapter for E2B's managed sandbox service
293
- * (`e2b.dev`). TS SDK does the scheduler work; namzu wraps it.
294
- * ~150ms cold-start (snapshot/restore). Apache-2.0 server side,
295
- * so the same code path can run against self-hosted E2B if
296
- * the host eventually wants to leave the managed service.
297
- * - `fly-machines` — adapter for Fly Machines
298
- * (`fly.io/docs/machines`). Closer to bare-metal control than
299
- * E2B; the right tier when the workload is "arbitrary tool
300
- * calls" rather than "Python REPL".
301
- * - `self-hosted` — direct `firecracker-containerd` against a
302
- * KVM-enabled host. For deployments where E2B and Fly are
303
- * both off the table for policy reasons. Operationally the
304
- * heaviest path; everything below ships first.
305
- *
306
- * Local dev: `e2b` and `fly-machines` work from any laptop with
307
- * an API key (no infra setup). `self-hosted` requires Linux + KVM
308
- * (Lima/Colima on macOS).
230
+ * `microvm` tier, against namzu's own guest orchestrator.
231
+ *
232
+ * Two adapters to third-party managed schedulers were declared here
233
+ * and never written: both threw on construction, and each demanded
234
+ * required credentials for a call that was never made. A config
235
+ * shape whose only reachable outcome is an exception is worse than
236
+ * no shape, because it type-checks.
237
+ *
238
+ * What remains is the orchestrator namzu runs: the control plane
239
+ * mints a guest per task and resumes it copy-on-write from a golden
240
+ * snapshot, so a cold start is a resume rather than a boot.
309
241
  */
310
- export type MicroVMBackendConfig =
311
- | {
312
- readonly tier: 'microvm'
313
- readonly service: 'e2b'
314
- readonly apiKey: string
315
- readonly template?: string
316
- }
317
- | {
318
- readonly tier: 'microvm'
319
- readonly service: 'fly-machines'
320
- readonly apiToken: string
321
- readonly app: string
322
- readonly image: string
323
- readonly region?: string
324
- }
325
- | {
326
- readonly tier: 'microvm'
327
- readonly service: 'self-hosted'
328
- readonly firecrackerBinary: string
329
- readonly kernelImage: string
330
- readonly rootfsImage: string
331
- /**
332
- * The owned-platform seam (ses_051). When these are present the
333
- * `self-hosted` arm targets the OWNED Azure Firecracker
334
- * orchestrator (`backends/firecracker/`), not a local
335
- * `firecracker-containerd`: `orchestratorEndpoint` is the
336
- * control-plane base URL, `getToken` mints a bearer for it
337
- * (the ACI `getArmToken` closure pattern, so this package keeps
338
- * zero Azure-SDK deps), and `template` selects the golden
339
- * snapshot revision to CoW-resume.
340
- *
341
- * The legacy local-`firecracker-containerd` shape
342
- * (`firecrackerBinary`/`kernelImage`/`rootfsImage` only) is
343
- * still NOT implemented and throws
344
- * {@link SandboxBackendNotImplementedError}; supplying
345
- * `orchestratorEndpoint` is what routes to the owned backend.
346
- */
347
- readonly orchestratorEndpoint?: string
348
- readonly getToken?: () => Promise<string>
349
- readonly template?: string
350
- /**
351
- * Resume this per-agent captured snapshot (layered on its base
352
- * golden) INSTEAD of a fresh golden boot. Tier-agnostic, additive,
353
- * optional: the backend that supports it (the owned firecracker
354
- * backend) honors it; others ignore it. Absent ⇒ the create body is
355
- * byte-identical and the generic golden-resume hot path is unchanged
356
- * (the field is only ever set by the host's per-agent trigger path).
357
- * Sibling to `template` (base-golden selector) — see
358
- * {@link AgentSnapshotRef}.
359
- */
360
- readonly agentSnapshot?: AgentSnapshotRef
361
- /** Fixed guest AF_VSOCK port the in-VM agent listens on. */
362
- readonly agentVsockPort?: number
363
- readonly readyTimeoutMs?: number
364
- readonly readyPollIntervalMs?: number
365
- /**
366
- * NETWORK-mode mTLS client material (ses_051 P4 client-proxy
367
- * bridge). When present, the orchestrator returns an `mtls` agent
368
- * handle (host/port/sandboxId, NO cert material) and this CA/cert/key
369
- * is MERGED onto that handle before the transport dials the per-host
370
- * relay over mTLS. Injected by the consumer's runtime (the Vandal
371
- * host layer reads it from `VANDAL_SANDBOX_FC_TLS_*`), NEVER fetched
372
- * inside this package — same dependency boundary as `getToken`, so
373
- * `@namzu/sandbox` stays Azure-SDK-free. Absent for the single-host
374
- * VSOCK default (the live proofs).
375
- */
376
- readonly mtls?: {
377
- readonly ca: string | Buffer
378
- readonly cert: string | Buffer
379
- readonly key: string | Buffer
380
- readonly servername?: string
381
- }
382
- /**
383
- * CONTROL-plane mTLS client material. When present, the orchestrator
384
- * control-plane calls (create/destroy POSTs to `orchestratorEndpoint`)
385
- * dial over mTLS — presenting this client cert and pinning this CA —
386
- * instead of plain `fetch`. Secures the control plane when
387
- * `orchestratorEndpoint` is an `https://` URL reached over the PUBLIC
388
- * internet (the non-VNet-integrated caller→FC-host hop), where the
389
- * shared-secret bearer alone would be exposed. The bearer is STILL sent
390
- * (defense in depth). Same `{ca,cert,key,servername}` shape + the same
391
- * consumer-injected dependency boundary as `mtls` (the one fleet CA
392
- * secures both planes). Absent → plain `fetch` control plane (the
393
- * single-host VSOCK default, unchanged).
394
- */
395
- readonly controlPlaneMtls?: {
396
- readonly ca: string | Buffer
397
- readonly cert: string | Buffer
398
- readonly key: string | Buffer
399
- readonly servername?: string
400
- }
401
- }
242
+ export type MicroVMBackendConfig = {
243
+ readonly tier: 'microvm'
244
+ readonly service: 'self-hosted'
245
+ /**
246
+ * Control-plane base URL, and the bearer minted for it.
247
+ *
248
+ * Both are REQUIRED, which is a correction: they were optional
249
+ * beside three required fields (`firecrackerBinary`,
250
+ * `kernelImage`, `rootfsImage`) belonging to a local-daemon shape
251
+ * that was never implemented. So the only working configuration
252
+ * had to supply three values nothing reads, and omitting these
253
+ * two type-checked its way to a runtime throw.
254
+ *
255
+ * `getToken` is a closure rather than a credential, so this
256
+ * package carries no cloud SDK: the host runtime owns how the
257
+ * bearer is obtained.
258
+ */
259
+ readonly orchestratorEndpoint: string
260
+ readonly getToken: () => Promise<string>
261
+ /** Golden snapshot revision to resume copy-on-write. */
262
+ readonly template?: string
263
+ /**
264
+ * Resume this per-agent captured snapshot (layered on its base
265
+ * golden) INSTEAD of a fresh golden boot. Tier-agnostic, additive,
266
+ * optional: the backend that supports it (the owned firecracker
267
+ * backend) honors it; others ignore it. Absent ⇒ the create body is
268
+ * byte-identical and the generic golden-resume hot path is unchanged
269
+ * (the field is only ever set by the host's per-agent trigger path).
270
+ * Sibling to `template` (base-golden selector) — see
271
+ * {@link AgentSnapshotRef}.
272
+ */
273
+ readonly agentSnapshot?: AgentSnapshotRef
274
+ /** Fixed guest AF_VSOCK port the in-VM agent listens on. */
275
+ readonly agentVsockPort?: number
276
+ readonly readyTimeoutMs?: number
277
+ readonly readyPollIntervalMs?: number
278
+ /**
279
+ * NETWORK-mode mTLS client material (ses_051 P4 client-proxy
280
+ * bridge). When present, the orchestrator returns an `mtls` agent
281
+ * handle (host/port/sandboxId, NO cert material) and this CA/cert/key
282
+ * is MERGED onto that handle before the transport dials the per-host
283
+ * relay over mTLS. Injected by the consumer's runtime (the Vandal
284
+ * host layer reads it from `VANDAL_SANDBOX_FC_TLS_*`), NEVER fetched
285
+ * inside this package — same dependency boundary as `getToken`, so
286
+ * `@namzu/sandbox` stays Azure-SDK-free. Absent for the single-host
287
+ * VSOCK default (the live proofs).
288
+ */
289
+ readonly mtls?: {
290
+ readonly ca: string | Buffer
291
+ readonly cert: string | Buffer
292
+ readonly key: string | Buffer
293
+ readonly servername?: string
294
+ }
295
+ /**
296
+ * CONTROL-plane mTLS client material. When present, the orchestrator
297
+ * control-plane calls (create/destroy POSTs to `orchestratorEndpoint`)
298
+ * dial over mTLS — presenting this client cert and pinning this CA —
299
+ * instead of plain `fetch`. Secures the control plane when
300
+ * `orchestratorEndpoint` is an `https://` URL reached over the PUBLIC
301
+ * internet (the non-VNet-integrated caller→FC-host hop), where the
302
+ * shared-secret bearer alone would be exposed. The bearer is STILL sent
303
+ * (defense in depth). Same `{ca,cert,key,servername}` shape + the same
304
+ * consumer-injected dependency boundary as `mtls` (the one fleet CA
305
+ * secures both planes). Absent → plain `fetch` control plane (the
306
+ * single-host VSOCK default, unchanged).
307
+ */
308
+ readonly controlPlaneMtls?: {
309
+ readonly ca: string | Buffer
310
+ readonly cert: string | Buffer
311
+ readonly key: string | Buffer
312
+ readonly servername?: string
313
+ }
314
+ }
402
315
 
403
316
  /**
404
317
  * A reference to a per-agent captured snapshot, layered on top of a base
@@ -421,14 +334,6 @@ export interface AgentSnapshotRef {
421
334
  readonly version: string
422
335
  }
423
336
 
424
- /**
425
- * `passthrough` tier. No isolation — runs commands directly in
426
- * the host process. Tests and trusted environments only.
427
- */
428
- export interface PassthroughBackendConfig {
429
- readonly tier: 'passthrough'
430
- }
431
-
432
337
  /**
433
338
  * Egress allowlist resolution. Host-supplied policy decides whether
434
339
  * an outbound request is allowed before the proxy opens a socket.
@@ -449,6 +354,17 @@ export interface PassthroughBackendConfig {
449
354
  * problem — the host owns the closure, the SDK runtime
450
355
  * doesn't have to forward identity through `provider.create`.
451
356
  */
357
+ export {
358
+ EgressProxy,
359
+ isHostAllowed,
360
+ splitAuthority,
361
+ } from './egress/index.js'
362
+ export type {
363
+ BrokeredCredential,
364
+ EgressProxyOptions,
365
+ RunningEgressProxy,
366
+ } from './egress/index.js'
367
+
452
368
  export type EgressPolicy =
453
369
  | { readonly kind: 'deny-all' }
454
370
  | { readonly kind: 'allow-all' }
@@ -547,7 +463,7 @@ export type SandboxProviderConfig =
547
463
  readonly layout: ContainerSandboxLayout
548
464
  })
549
465
  | (SandboxProviderConfigBase & {
550
- readonly backend: ProcessBackendConfig | MicroVMBackendConfig | PassthroughBackendConfig
466
+ readonly backend: MicroVMBackendConfig
551
467
  })
552
468
 
553
469
  interface SandboxProviderConfigBase {
@@ -564,25 +480,22 @@ interface SandboxProviderConfigBase {
564
480
  * the chosen backend.
565
481
  *
566
482
  * Backends are loaded lazily — the package only imports the
567
- * platform-specific modules (Anthropic's sandbox-runtime, the
568
- * Docker SDK, the E2B SDK, …) when the corresponding backend is
483
+ * platform-specific modules (the host sandbox runtime, the
484
+ * Docker SDK, the microVM SDK, …) when the corresponding backend is
569
485
  * requested. That keeps `@namzu/sandbox` reasonable to install in
570
486
  * environments where one backend is genuinely impossible.
571
487
  *
572
- * **Not implemented in this commit** — this file declares the
573
- * surface; backends arrive in subsequent commits per the ses_004
574
- * phase plan:
575
- *
576
- * - **P3.1** — `container` (docker runtime). Phase 1: ship now.
577
- * - **P3.2** — `EgressPolicy` plumbing + reference egress proxy.
578
- * - **P3.3** — `microvm` (E2B + Fly Machines adapters). Phase 2.
579
- * - **P3.4** — `process` (Anthropic sandbox-runtime adapter).
580
- * - **P3.5** — `microvm` (self-hosted firecracker-containerd) +
581
- * `container` (gVisor runtime). Phase 3 adversarial-multi-tenant.
582
- *
583
- * Calling this function now throws
584
- * {@link SandboxBackendNotImplementedError} so consumers get a
585
- * clear signal during the staged rollout.
488
+ * Every shape in {@link SandboxBackendConfig} has a backend behind
489
+ * it. That is a recent property: this file used to declare a staged
490
+ * roadmap of tiers and adapters, most of which threw, so the surface
491
+ * described a plan and the runtime described the truth. The shapes
492
+ * that were never built are gone rather than pending — a config that
493
+ * type-checks and can only throw teaches a caller the wrong thing
494
+ * about what this package does.
495
+ *
496
+ * {@link SandboxBackendNotImplementedError} survives for the untyped
497
+ * caller: a JS host that invents a tier gets a named refusal instead
498
+ * of a provider that confines nothing.
586
499
  */
587
500
  export function createSandboxProvider(config: SandboxProviderConfig): SandboxProvider {
588
501
  const backend = pickBackend(config)
@@ -723,13 +636,11 @@ function pickBackend(config: SandboxProviderConfig): SandboxBackend {
723
636
  /**
724
637
  * Human-readable backend label for error messages. Returns the
725
638
  * tier plus the concrete service / runtime when present, e.g.
726
- * `'microvm:e2b'` or `'container:runsc'`.
639
+ * `'microvm:self-hosted'` or `'container:runsc'`.
727
640
  */
728
641
  function describeBackend(config: SandboxBackendConfig): string {
729
642
  if (config.tier === 'microvm') return `microvm:${config.service}`
730
- if (config.tier === 'container') return `container:${config.runtime ?? 'docker'}`
731
- if (config.tier === 'process') return `process:${config.engine ?? 'auto'}`
732
- return config.tier
643
+ return `container:${config.runtime ?? 'docker'}`
733
644
  }
734
645
 
735
646
  // ---------------------------------------------------------------------------