@namzu/sandbox 1.1.0

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 (69) hide show
  1. package/CHANGELOG.md +474 -0
  2. package/LICENSE.md +110 -0
  3. package/README.md +148 -0
  4. package/dist/backends/aci-standby-pool/index.d.ts +104 -0
  5. package/dist/backends/aci-standby-pool/index.d.ts.map +1 -0
  6. package/dist/backends/aci-standby-pool/index.js +425 -0
  7. package/dist/backends/aci-standby-pool/index.js.map +1 -0
  8. package/dist/backends/docker/__tests__/leaf-permissions.smoke.test.d.ts +40 -0
  9. package/dist/backends/docker/__tests__/leaf-permissions.smoke.test.d.ts.map +1 -0
  10. package/dist/backends/docker/__tests__/leaf-permissions.smoke.test.js +157 -0
  11. package/dist/backends/docker/__tests__/leaf-permissions.smoke.test.js.map +1 -0
  12. package/dist/backends/docker/index.d.ts +118 -0
  13. package/dist/backends/docker/index.d.ts.map +1 -0
  14. package/dist/backends/docker/index.js +645 -0
  15. package/dist/backends/docker/index.js.map +1 -0
  16. package/dist/backends/firecracker/__tests__/backend.test.d.ts +13 -0
  17. package/dist/backends/firecracker/__tests__/backend.test.d.ts.map +1 -0
  18. package/dist/backends/firecracker/__tests__/backend.test.js +353 -0
  19. package/dist/backends/firecracker/__tests__/backend.test.js.map +1 -0
  20. package/dist/backends/firecracker/__tests__/control-plane-mtls.test.d.ts +19 -0
  21. package/dist/backends/firecracker/__tests__/control-plane-mtls.test.d.ts.map +1 -0
  22. package/dist/backends/firecracker/__tests__/control-plane-mtls.test.js +201 -0
  23. package/dist/backends/firecracker/__tests__/control-plane-mtls.test.js.map +1 -0
  24. package/dist/backends/firecracker/__tests__/fixtures/mtls-pki.d.ts +39 -0
  25. package/dist/backends/firecracker/__tests__/fixtures/mtls-pki.d.ts.map +1 -0
  26. package/dist/backends/firecracker/__tests__/fixtures/mtls-pki.js +149 -0
  27. package/dist/backends/firecracker/__tests__/fixtures/mtls-pki.js.map +1 -0
  28. package/dist/backends/firecracker/__tests__/protocol.test.d.ts +6 -0
  29. package/dist/backends/firecracker/__tests__/protocol.test.d.ts.map +1 -0
  30. package/dist/backends/firecracker/__tests__/protocol.test.js +77 -0
  31. package/dist/backends/firecracker/__tests__/protocol.test.js.map +1 -0
  32. package/dist/backends/firecracker/__tests__/transport.test.d.ts +20 -0
  33. package/dist/backends/firecracker/__tests__/transport.test.d.ts.map +1 -0
  34. package/dist/backends/firecracker/__tests__/transport.test.js +449 -0
  35. package/dist/backends/firecracker/__tests__/transport.test.js.map +1 -0
  36. package/dist/backends/firecracker/index.d.ts +124 -0
  37. package/dist/backends/firecracker/index.d.ts.map +1 -0
  38. package/dist/backends/firecracker/index.js +334 -0
  39. package/dist/backends/firecracker/index.js.map +1 -0
  40. package/dist/backends/firecracker/protocol.d.ts +132 -0
  41. package/dist/backends/firecracker/protocol.d.ts.map +1 -0
  42. package/dist/backends/firecracker/protocol.js +112 -0
  43. package/dist/backends/firecracker/protocol.js.map +1 -0
  44. package/dist/backends/firecracker/transport.d.ts +251 -0
  45. package/dist/backends/firecracker/transport.d.ts.map +1 -0
  46. package/dist/backends/firecracker/transport.js +524 -0
  47. package/dist/backends/firecracker/transport.js.map +1 -0
  48. package/dist/index.d.ts +611 -0
  49. package/dist/index.d.ts.map +1 -0
  50. package/dist/index.js +376 -0
  51. package/dist/index.js.map +1 -0
  52. package/dist/index.test.d.ts +28 -0
  53. package/dist/index.test.d.ts.map +1 -0
  54. package/dist/index.test.js +670 -0
  55. package/dist/index.test.js.map +1 -0
  56. package/package.json +54 -0
  57. package/src/backends/aci-standby-pool/index.ts +602 -0
  58. package/src/backends/docker/__tests__/leaf-permissions.smoke.test.ts +169 -0
  59. package/src/backends/docker/index.ts +826 -0
  60. package/src/backends/firecracker/__tests__/backend.test.ts +418 -0
  61. package/src/backends/firecracker/__tests__/control-plane-mtls.test.ts +253 -0
  62. package/src/backends/firecracker/__tests__/fixtures/mtls-pki.ts +166 -0
  63. package/src/backends/firecracker/__tests__/protocol.test.ts +90 -0
  64. package/src/backends/firecracker/__tests__/transport.test.ts +526 -0
  65. package/src/backends/firecracker/index.ts +528 -0
  66. package/src/backends/firecracker/protocol.ts +191 -0
  67. package/src/backends/firecracker/transport.ts +667 -0
  68. package/src/index.test.ts +731 -0
  69. package/src/index.ts +930 -0
package/CHANGELOG.md ADDED
@@ -0,0 +1,474 @@
1
+ # @namzu/sandbox
2
+
3
+ ## 1.1.0
4
+
5
+ ### Minor Changes
6
+
7
+ - ff1e013: Add an additive control-plane mTLS dial to the Firecracker backend.
8
+
9
+ `FirecrackerBackendInternalConfig` gains an optional `controlPlaneMtls`
10
+ (`{ ca; cert; key; servername? }`, the SAME shape as the relay's `mtls`). When
11
+ present, the orchestrator control-plane calls — `POST /sandboxes`,
12
+ `DELETE /sandboxes/{id}:delete` — dial over a `node:https` request that presents
13
+ the client cert and verifies the orchestrator's server cert against the injected
14
+ CA (`rejectUnauthorized: true`, `minVersion: TLSv1.3`), INSTEAD of the plain
15
+ global `fetch`. This secures the control plane when `orchestratorEndpoint` is an
16
+ `https://` URL reached over the PUBLIC internet (the non-VNet-integrated
17
+ caller→FC-host hop), where the shared-secret bearer alone would be exposed on
18
+ the wire.
19
+
20
+ The change is purely additive and opt-in: with no `controlPlaneMtls` injected,
21
+ the EXISTING plain-`fetch` control-plane path runs byte-for-byte unchanged (the
22
+ single-host live proofs + local dev). The shared-secret bearer is still sent in
23
+ both modes — mTLS is defense in depth on top, not a replacement. `node:https` is
24
+ used rather than a `fetch` + undici dispatcher because the package declares no
25
+ undici dependency; `node:https` is always importable and adds nothing. The cert
26
+ material is injected by the consumer's runtime (mirrors `getToken` and the relay
27
+ `mtls`), so the package still reads no keys from disk and stays Azure-SDK free.
28
+
29
+ - 208d415: Add an `mtls` arm to the Firecracker agent transport for the cross-host
30
+ client-proxy bridge.
31
+
32
+ `SandboxAgentHandle` gains a third variant —
33
+ `{ kind: 'mtls'; host; port; sandboxId; tls: { ca; cert; key; servername? } }` —
34
+ alongside the existing `unix` and `vsock` arms. When the orchestrator runs on a
35
+ different host from the caller (the owned-fleet production path), the host-local
36
+ `v.sock` is unreachable over the network, so the dialer instead `tls.connect()`s
37
+ to a per-FC-host mTLS relay, writes a `SANDBOX <sandboxId>\n` preamble, and then
38
+ runs the IDENTICAL length-framed NDJSON loop. The relay terminates mTLS and
39
+ bridges to the jailed `v.sock` (issuing the guest `CONNECT 1024` handshake
40
+ itself), so one inbound mTLS connection maps to one fresh local `v.sock`
41
+ connect — preserving the resume-survival property of opening a fresh connection
42
+ per request.
43
+
44
+ The change is purely additive: the `unix` and `vsock` arms and all framing,
45
+ heartbeat, and reconnect-on-resume code are byte-for-byte unchanged (single-host
46
+ deployments keep using `vsock`). The TLS material is injected by the consumer
47
+ (never returned by the orchestrator), keeping the package free of any key
48
+ management.
49
+
50
+ - 74a1198: Add the owned-Firecracker microVM backend (`microvm:self-hosted`) and its
51
+ host-side vsock transport.
52
+
53
+ The `MicroVMBackendConfig` `self-hosted` arm gains the owned-platform seam:
54
+ `orchestratorEndpoint` + `getToken` (the ACI `getArmToken` closure pattern, so
55
+ the package keeps zero Azure-SDK deps) route to a new `backends/firecracker/`
56
+ backend instead of throwing `SandboxBackendNotImplementedError`; `template`
57
+ selects the golden snapshot revision and `agentVsockPort` /
58
+ `readyTimeoutMs` / `readyPollIntervalMs` tune the agent dial. The legacy local
59
+ `firecracker-containerd` shape (the three image fields alone) still throws.
60
+
61
+ The backend is a sibling of `docker/` and `aci-standby-pool/` and a
62
+ remote-copy backend like ACI (workspace seeded by archive-sync over the control
63
+ channel, no host bind-mounts). It speaks the SAME NDJSON exec-stream + base64
64
+ file-IO wire as the docker/ACI HTTP worker — only the transport differs:
65
+
66
+ - One wire contract, factored into `backends/firecracker/protocol.ts`
67
+ (`ExecRequest`, the `stdout_delta`/`stderr_delta`/`result`/`error` `ExecEvent`
68
+ union, `ReadFileRequest`/`WriteFileRequest` + responses, the
69
+ `ExecResultAccumulator` and `parseExecLine` the docker loop inlines today).
70
+ - Two transports: HTTP for docker/ACI (UNCHANGED), and a NEW framed-over-vsock
71
+ transport for FC (`backends/firecracker/transport.ts`), because across an FC
72
+ snapshot resume a TCP control channel is dead-on-arrival while the vsock
73
+ LISTEN socket survives (FC `snapshot-support.md`). Node `fetch` cannot dial
74
+ AF_VSOCK, so the dialer, length-framing, heartbeat, and the
75
+ reconnect-on-resume hardening (per-attempt connect/handshake timeout + retry
76
+ budget to survive the FC #4713 `TRANSPORT_RESET`-not-delivered hang) are new.
77
+
78
+ New public exports from `@namzu/sandbox`: `VsockAgentTransport`,
79
+ `SandboxAgentHandle`, `VsockTransportOptions`, `FirecrackerBackendInternalConfig`,
80
+ `OrchestratorTokenProvider`. The in-VM agent source (`agent/agent.cjs`, a vsock
81
+ server reusing the worker spawn/jail + NDJSON shapes verbatim with the mandatory
82
+ pre-ready entropy reseed) ships in the repo as a golden-rootfs build input,
83
+ mirroring how `worker/server.js` is baked into the docker image — it is not a
84
+ published runtime dependency.
85
+
86
+ ### Patch Changes
87
+
88
+ - 0d1fb7b: Harden file intake and ACI readiness failure handling.
89
+
90
+ The built-in read tool now guides Office and PDF packages through
91
+ extractor tooling instead of treating binary document containers as
92
+ UTF-8 text. The ACI Standby Pool backend now deletes a claimed
93
+ container group when IP or worker readiness polling fails before a
94
+ Sandbox handle is returned.
95
+
96
+ ## 1.0.0
97
+
98
+ ### Major Changes
99
+
100
+ - 8fd9349: feat(sandbox)!: Anthropic-style multi-mount container sandbox layout
101
+
102
+ Adds a declarative `ContainerSandboxLayout` shape that maps onto
103
+ Anthropic's container architecture (Claude container blueprint,
104
+ Code Interpreter, "skills"). The `Container` prefix is load-bearing
105
+ — this layout is specific to the container tier; future microVM /
106
+ process tiers will carry their own layout types when their adapters
107
+ land. Layout is supplied at provider construction — not per
108
+ `provider.create()` call — so the type system catches missing-layout
109
+ mistakes at compile time:
110
+
111
+ ```ts
112
+ import {
113
+ createSandboxProvider,
114
+ SANDBOX_DEFAULT_OUTPUTS_PATH, // re-exported from @namzu/sdk
115
+ } from "@namzu/sandbox";
116
+
117
+ const provider = createSandboxProvider({
118
+ backend: { tier: "container", image: "namzu-worker:latest" },
119
+ layout: {
120
+ outputs: {
121
+ source: {
122
+ type: "hostDir",
123
+ hostPath: "/var/lib/vandal/sessions/<task>/outputs",
124
+ },
125
+ },
126
+ uploads: {
127
+ source: {
128
+ type: "hostDir",
129
+ hostPath: "/var/lib/vandal/sessions/<task>/uploads",
130
+ },
131
+ },
132
+ skills: [
133
+ {
134
+ id: "pdf-tools",
135
+ source: { type: "hostDir", hostPath: "/opt/skills/pdf-tools" },
136
+ },
137
+ ],
138
+ },
139
+ });
140
+ ```
141
+
142
+ Each mount carries a discriminated `ContainerSandboxMountSource`.
143
+ The single variant today is `{ type: 'hostDir'; hostPath: string }`;
144
+ future variants (squashfs skill bundles, managed volumes attached
145
+ to a container backend) land additively as minor bumps without
146
+ reshaping the consumer call site.
147
+
148
+ Layout fields and their defaults:
149
+
150
+ - `outputs` — RW. Default `/mnt/user-data/outputs`. **Required**.
151
+ - `uploads` — RO. Default `/mnt/user-data/uploads`.
152
+ - `toolResults` — RO. Default `/mnt/user-data/tool_results`.
153
+ - `skills` — RO list, default `/mnt/skills/<id>` per entry.
154
+ - `transcripts` — RO. Default `/mnt/transcripts`.
155
+
156
+ The defaults are exported as constants from `@namzu/sdk`'s root
157
+ barrel (`SANDBOX_DEFAULT_OUTPUTS_PATH`,
158
+ `SANDBOX_DEFAULT_UPLOADS_PATH`, `SANDBOX_DEFAULT_TOOL_RESULTS_PATH`,
159
+ `SANDBOX_DEFAULT_TRANSCRIPTS_PATH`, `SANDBOX_DEFAULT_SKILLS_PARENT`)
160
+ and re-exported from `@namzu/sandbox`, so prompt-template generators
161
+ and the backend agree on a single source of truth. Both import
162
+ paths (`@namzu/sdk` and `@namzu/sandbox`) are pinned by tests.
163
+
164
+ There is intentionally **no `scratchpad` field**: the
165
+ container-internal RW area (`/home/<imageUser>`) is image-bake
166
+ responsibility, not a runtime knob.
167
+
168
+ **Validation** runs synchronously inside `createSandboxProvider` and
169
+ collects every violation in one
170
+ `ContainerSandboxLayoutValidationError.reasons[]`:
171
+
172
+ - `outputs` must be present.
173
+ - Skill IDs match `/^[a-zA-Z0-9_.-]+$/`, and `id.includes('..')` is
174
+ rejected (path-traversal guard — covers `..`, `foo..bar`,
175
+ `..foo`, `foo..`). Isolated dots (`pdf-tools.v2`) pass.
176
+ - Skill IDs are unique.
177
+ - Resolved `containerPath`s are unique across every mount slot.
178
+
179
+ **Error transport.** `ContainerSandboxLayoutValidationError`
180
+ carries a `cause` field (Error native), `toJSON()` keeps `reasons`
181
+ (and `cause` when set), and a new helper
182
+ `serializeSandboxError(err: unknown): SerializedSandboxError`
183
+ returns a plain object that survives `structuredClone`,
184
+ `postMessage`, and `JSON.stringify` round-trips uniformly. The
185
+ helper is **cycle-safe** — a `WeakSet`-threaded recursion detects
186
+ self-cycles (`a.cause = a`), two-node cycles (`a.cause = b;
187
+ b.cause = a`), and longer loops, replacing the offending node with
188
+ a `{ name: 'CircularReference', message: '[circular]' }` sentinel
189
+ rather than overflowing the stack. The helper is also
190
+ **transport-safe** — non-Error causes (Function, Symbol, BigInt,
191
+ NaN, ±Infinity, undefined, null, primitives, plain objects) are
192
+ converted to a typed envelope by `serializeNonErrorCause` BEFORE
193
+ they enter the wire shape, so values that `JSON.stringify` drops
194
+ silently or `structuredClone` throws on never appear.
195
+ `SerializedSandboxError.cause` is strictly typed
196
+ `SerializedSandboxError | undefined`. Use the helper at any
197
+ worker / IPC / log-shipper boundary; cloning the Error subclass
198
+ itself is not supported.
199
+
200
+ **Breaking changes** — the legacy single-mount paradigm is removed:
201
+
202
+ - `SandboxCreateConfig.hostWorkspaceDir` is removed. Pass the host
203
+ path on `layout.outputs.source.hostPath` at provider construction.
204
+ - `ContainerBackendConfig.workspaceMount` is removed. Pass the
205
+ in-container path on `layout.outputs.containerPath`.
206
+ - `SandboxProviderConfig` is now a discriminated union: the
207
+ container variant requires `layout: ContainerSandboxLayout`, the
208
+ other variants do not carry the field. Constructing a docker
209
+ provider without a layout fails at compile time.
210
+ - `SandboxCreateConfig.layout` does NOT exist; layout is
211
+ factory-baked. The SDK runtime cannot accidentally call a
212
+ container provider without a layout.
213
+ - The docker backend no longer allocates host directories
214
+ (`mkdtemp`) or removes them on `destroy()`. Every bind source is
215
+ consumer-owned. This also fixes an `EACCES: permission denied,
216
+ mkdir '/Users'` crash that hit sibling-container deployments
217
+ (Vandal Cowork).
218
+ - The worker no longer reads `NAMZU_SANDBOX_LAYOUT` (it never
219
+ branched on the env, only logged it; size grew with the skill
220
+ list). Only `NAMZU_SANDBOX_WORKSPACE` is forwarded today.
221
+
222
+ The reference Dockerfile pre-creates **only the parent directories**
223
+ `/mnt`, `/mnt/user-data`, `/mnt/skills` — root-owned, mode 0555.
224
+ Leaf paths (`outputs/`, `uploads/`, `tool_results/`, `transcripts/`,
225
+ `<skill-id>/`) are intentionally NOT pre-created. When a bind is
226
+ attached the docker daemon creates the leaf as the bind target;
227
+ when not attached, the leaf does not exist — the model gets ENOENT
228
+ instead of an empty writable dir that looks "mounted but uploaded
229
+ nothing".
230
+
231
+ `pnpm sandbox:smoke` (alias for `pnpm --filter @namzu/sandbox
232
+ test:smoke`) runs an opt-in docker integration test exercising the
233
+ leaf-permission contract against a real docker daemon. Excluded
234
+ from the default `pnpm test`; gated by a dedicated
235
+ `.github/workflows/sandbox-smoke.yml` workflow that builds the
236
+ reference image and runs the smoke test on PR / push when the
237
+ sandbox surface changes. On CI (`process.env.CI === 'true'`), the
238
+ smoke test fails fast if docker / the image are absent rather than
239
+ silently skipping.
240
+
241
+ `@namzu/sdk` exports `ContainerSandboxLayout`,
242
+ `ContainerSandboxLayoutMount`, `ContainerSandboxMountSource`,
243
+ `ContainerSandboxSkillMount`, `ResolvedContainerSandboxLayout`,
244
+ and the five `SANDBOX_DEFAULT_*_PATH` constants from its root
245
+ barrel. `@namzu/sandbox` re-exports those names plus
246
+ `ContainerSandboxLayoutValidationError`, `serializeSandboxError`,
247
+ and the `SerializedSandboxError` shape. The packed-tarball shape
248
+ is verified by `.github/scripts/verify-consumer-install.sh`'s
249
+ `@namzu/sandbox public-surface fixture`, which installs the
250
+ package from a tarball into a clean project and asserts every
251
+ documented constant + runtime export comes back via both
252
+ `@namzu/sandbox` and `@namzu/sdk` import paths. `@namzu/sandbox`
253
+ is also added to `ci.yml`'s `publint` and ATTW (Are The Types
254
+ Wrong) gates.
255
+
256
+ ### Minor Changes
257
+
258
+ - 04551a8: feat(sandbox): `container:docker` backend implementation
259
+
260
+ P3.1 — first concrete backend lands. `createSandboxProvider({ backend: { tier: 'container', runtime: 'docker', image } })` now returns a working `SandboxProvider`:
261
+
262
+ - Spawns one Docker container per `Sandbox` instance via the `docker` CLI (no node-docker SDK dep — keeps the package thin).
263
+ - Container runs the small HTTP worker shipped under `packages/sandbox/worker/server.js`. The host adapter talks to it on `127.0.0.1:<random-port>`.
264
+ - Worker exposes `/healthz` (liveness), `/execute` (NDJSON-streamed command run), `/read-file`, `/write-file`. All `Sandbox` interface methods route through these.
265
+ - Container goes away on `Sandbox.destroy()` (`docker rm -f`).
266
+ - Workspace bind-mount under `/tmp/namzu-sandbox-<id>-*` cleaned up on destroy.
267
+ - Resource caps from `SandboxBackendOptions` map to Docker flags: `memoryLimitMb` → `--memory`, `maxProcesses` → `--pids-limit`. Default network is `none` (egress proxy plumbing is P3.2).
268
+
269
+ Reference Dockerfile (`packages/sandbox/worker/Dockerfile`) ships with a comprehensive pre-installed toolchain so a greenfield namzu deployment "just works" against the typical agent workload:
270
+
271
+ - **Office IO**: openpyxl, xlsxwriter, python-docx, python-pptx, pypdf, reportlab, pdfplumber, pymupdf, pdf2image, docx2pdf.
272
+ - **Rendering**: weasyprint, pydyf, markdown, jinja2, beautifulsoup4, lxml, html5lib.
273
+ - **Data**: pandas, polars, numpy, pyarrow, duckdb, sqlalchemy.
274
+ - **Charting**: matplotlib, plotly, seaborn, kaleido.
275
+ - **ML / stats**: scikit-learn, statsmodels, scipy.
276
+ - **OCR / image**: pytesseract, Pillow, opencv-python-headless.
277
+ - **OR / planning**: ortools, pulp, simpy, networkx, workalendar.
278
+ - **HTTP**: requests, httpx, aiohttp.
279
+ - **System tools**: LibreOffice, pandoc, Ghostscript, qpdf, poppler-utils, tesseract (eng+tur), ImageMagick, exiftool, optipng, jpegoptim, graphviz, Chromium (+ chromium-driver), ripgrep, jq, yq, tree, htop.
280
+ - **Node toolchain**: `@mermaid-js/mermaid-cli`, xlsx, docx, pptxgenjs, pdf-lib, sharp, markdown-it, dompurify, jsdom.
281
+ - **Fonts**: Noto (Latin + CJK + emoji + symbol), Liberation, DejaVu, FreeFont — Turkish-friendly.
282
+ - **Distro**: Debian Bookworm slim, not Alpine — manylinux wheel coverage matters for the doc-gen path; compass-platform hit musl issues on the same workload.
283
+
284
+ Hosts that want a leaner image build their own and reference it via `ContainerBackendConfig.image`. The fat default exists so the agent isn't told to use a tool that doesn't exist (the prompt-vs-runtime drift class of bugs Codex flagged repeatedly in the Vandal Cowork iterations).
285
+
286
+ Trust model: container is the trust boundary; worker listens on loopback inside its own netns; outbound network defaults to `none` until the egress proxy lands in P3.2. Worker runs as non-root (`namzu:1001`) inside the container; host mounts `/workspace` writable to that uid.
287
+
288
+ - 663f504: feat(sandbox): new package — pluggable SandboxProvider for @namzu/sdk
289
+
290
+ Introduces a new workspace package `@namzu/sandbox` that wraps the
291
+ `SandboxProvider` shape `@namzu/sdk` already declares with concrete
292
+ backends. Sandbox is intentionally split off the core SDK because:
293
+
294
+ - Native dependencies (`bubblewrap` binary, seccomp filter generation,
295
+ Docker SDK, parent-proxy machinery) shouldn't pollute every namzu
296
+ consumer.
297
+ - Anthropic itself ships their sandbox runtime as a separate package
298
+ (`@anthropic-ai/sandbox-runtime`) for the same reason.
299
+ - Hosts that don't need isolation (tests, trusted environments) can
300
+ skip installing it.
301
+
302
+ This commit is the **public-surface skeleton** — the package is
303
+ declared, the contract is fixed, but no backend is implemented yet.
304
+ Calling `createSandboxProvider({ backend })` throws
305
+ `SandboxBackendNotImplementedError` for every backend tag. Backends
306
+ arrive in subsequent commits per the
307
+ `ses_004-native-agentic-runtime-and-sandbox` design session:
308
+
309
+ - **P3.1** — `process` backend (Anthropic sandbox-runtime adapter).
310
+ - **P3.2** — `EgressPolicy` plumbing with the proxy daemon.
311
+ - **P3.3** — `container` backend (compass-platform pattern).
312
+
313
+ The exported surface freezes:
314
+
315
+ - `SandboxBackendKind = 'process' | 'container' | 'passthrough'`
316
+ - `EgressPolicy` (deny-all / allow-all / static / resolver)
317
+ - `SandboxBackend` and `SandboxBackendOptions`
318
+ - `SandboxProviderConfig` and `createSandboxProvider`
319
+ - `SandboxBackendNotImplementedError`
320
+
321
+ - 274bcfa: feat(sandbox)!: tiered backend taxonomy aligned with 2026 industrial standard
322
+
323
+ Restructures the public surface from a flat backend-tag list into a
324
+ four-tier taxonomy that mirrors how production agent platforms
325
+ actually deploy code-execution sandboxes:
326
+
327
+ - `process` — Claude Code-style host-process isolation
328
+ (bubblewrap on Linux, Seatbelt on macOS, via Anthropic's
329
+ `@anthropic-ai/sandbox-runtime`). For agents that run on the
330
+ developer's own machine.
331
+ - `container` — OCI container per task. Two runtime options:
332
+ `docker` (default, universal local-dev fallback; what
333
+ Northflank/Railway/Render/Compass-platform/GitHub Actions
334
+ runners ship) and `runsc` (Google gVisor, trusted-tenant tier;
335
+ what OpenAI Code Interpreter and Modal Labs ship).
336
+ - `microvm` — Firecracker microVM per task, three concrete
337
+ services: `e2b` (managed, ~150ms cold-start via snapshot
338
+ restore), `fly-machines` (managed, closer to bare-metal), and
339
+ `self-hosted` (`firecracker-containerd` on KVM-enabled Linux for
340
+ hosts that need to own the scheduler).
341
+ - `passthrough` — no isolation; for tests and explicitly trusted
342
+ environments.
343
+
344
+ Each tier carries a tier-specific config shape (discriminated union
345
+ on `tier`); picking a tier picks the shape automatically via TS
346
+ narrowing. Industrial precedent for every choice is cited in the
347
+ package README:
348
+
349
+ - Adversarial multi-tenant → Firecracker microVMs (AWS Lambda /
350
+ Fargate, Fly Machines, Replit, E2B, Daytona — Fly's
351
+ "Sandboxing and Workload Isolation" post and the original
352
+ Firecracker NSDI '20 paper are the canonical refs).
353
+ - Trusted-tenant → gVisor (GKE Sandbox, Modal, OpenAI Code
354
+ Interpreter — `gvisor.dev/docs/architecture_guide/security` is
355
+ the reference).
356
+ - Single-user developer machine → bubblewrap / Seatbelt
357
+ (Anthropic Claude Code — `anthropic-experimental/sandbox-runtime`).
358
+ - Single-tenant or co-trusted → plain Docker + seccomp default
359
+ profile.
360
+
361
+ We deliberately do NOT build our own Firecracker scheduler — that
362
+ is E2B's and Fly's entire product, and writing our own would be a
363
+ years-long detour. The `microvm` tier adapts to theirs and
364
+ reserves `self-hosted` for compliance/air-gap deployments.
365
+
366
+ `EgressPolicy.resolver` is now parameterless
367
+ (`() => Promise<string[]>`). Per Codex's stop-time review, the
368
+ prior shape took a `EgressResolveContext` with `tenantId` /
369
+ `runId` / `agentId` fields the SDK runtime had no way to populate,
370
+ so the resolver context was permanently unreachable. Hosts that
371
+ need per-tenant policies bake the tenant into the closure that
372
+ constructs the provider — exactly how compass-platform's
373
+ JWT-minting flow already works.
374
+
375
+ Same reason for dropping `tenantId` / `runId` / `agentId` from
376
+ `SandboxBackendOptions`: a contract the runtime can't fulfill is
377
+ worse than not having it.
378
+
379
+ **Breaking** for consumers of the still-pre-1.0 surface introduced
380
+ in the previous skeleton commit (no implementations existed yet,
381
+ so realistic migration cost is zero).
382
+
383
+ Phase plan unchanged in structure but renumbered for clarity:
384
+ P3.1 ships `container:docker` first (works locally and in any
385
+ cloud), P3.2 the egress proxy, P3.3 the `microvm` managed adapters,
386
+ P3.4 the `process` tier, P3.5 the adversarial-multi-tenant tier.
387
+
388
+ ### Patch Changes
389
+
390
+ - 8022011: fix(sandbox): docker backend lifecycle leak + worker symlink escape
391
+
392
+ Two issues Codex stop-time review caught on the just-shipped
393
+ `container:docker` backend (#32):
394
+
395
+ **HIGH — container lifecycle leak.** `spawnDockerSandbox`'s create
396
+ path had no rollback on failure. If `docker run` succeeded but
397
+ `/healthz` polling timed out (slow image, kernel under pressure,
398
+ network-namespace setup hiccup), the temp workspace under `/tmp/`
399
+ plus the running container were both orphaned. The
400
+ `reservePort()` pattern also had a TOCTOU race: this process
401
+ allocated a host port, closed the listening socket, then passed
402
+ the number to `docker run --publish 127.0.0.1:PORT:…`, leaving a
403
+ window where another process could bind the same port.
404
+
405
+ Fixed:
406
+
407
+ - `spawnDockerSandbox` now wraps create in `try/catch`. The catch
408
+ arm runs `cleanupOnFailure()` which `docker rm -f`s the
409
+ container if it started and removes `hostWorkspace` if it was
410
+ created. Both are tracked via a flag/var captured in the outer
411
+ scope.
412
+ - Switched from pre-reserve-then-publish to letting Docker
413
+ allocate via `--publish 127.0.0.1::WORKER_PORT`. The mapped
414
+ host port is read back via `docker inspect --format
415
+ '{{(index ...).HostPort}}'`. No TOCTOU window.
416
+
417
+ **MEDIUM — symlink escape in worker.** `resolveWithinWorkspace()`
418
+ in the worker's `/read-file` and `/write-file` handlers checked
419
+ the lexical path string but `fs.readFile` / `fs.writeFile`
420
+ follow symlinks. A symlink inside `/workspace` pointing to
421
+ `/etc/passwd` (or anywhere outside the bind-mount) bypassed the
422
+ boundary.
423
+
424
+ Fixed: added `realpathWithinWorkspace()` which `realpath`s both
425
+ the workspace root and the requested target, then verifies the
426
+ resolved real path is still inside the workspace. For writes
427
+ where the target may not exist yet, the parent directory's
428
+ realpath is checked instead. Both handlers now resolve through
429
+ the new helper before touching the file.
430
+
431
+ - 63e44f7: Worker `handleExecute` no longer crashes the per-task container when a
432
+ single request body is rejected by `resolveWithinWorkspace` (e.g. a host
433
+ path forwarded as `cwd`) or by the workspace `mkdir`. Each fallible step
434
+ now returns a typed `400` (or a terminal NDJSON `error` event for
435
+ post-headers failures) and the worker stays alive for the next call —
436
+ prior behaviour was an unhandled rejection on the `http.createServer`
437
+ callback, which on Node ≥ 15 exits the process and gives every
438
+ subsequent SDK call the bare `fetch failed` from `UND_ERR_SOCKET`.
439
+
440
+ The docker backend's host-side `execViaWorker` and `writeFile` fetches
441
+ now surface `error.cause.code` / `cause.message` instead of the
442
+ stripped `fetch failed`. The bash builtin no longer forwards
443
+ `context.workingDirectory` (a host-side path that has no meaning
444
+ inside the sandbox container) as `cwd`; tools that need a sub-cwd
445
+ inside the sandbox can be added later via an explicit
446
+ `SandboxExecOptions` field.
447
+
448
+ The SDK's iteration aggregator now derives
449
+ `ChatCompletionResponse.toolCalls[i].function.arguments` from each
450
+ bucket's parsed input rather than the raw `argsBuf` buffer. When a
451
+ provider stream truncates with `stop_reason: "max_tokens"` mid-
452
+ `input_json_delta`, downstream `JSON.parse` in
453
+ `runtime/query/executor.ts:executeSingle` no longer rejects with the
454
+ generic "Invalid JSON in tool arguments" — the tool runs against the
455
+ empty parsed object and the input zod schema produces a readable
456
+ "<field> is required" error instead.
457
+
458
+ - Updated dependencies [542f057]
459
+ - Updated dependencies [df09910]
460
+ - Updated dependencies [140bcc0]
461
+ - Updated dependencies [ea21863]
462
+ - Updated dependencies [38c4b62]
463
+ - Updated dependencies [265150b]
464
+ - Updated dependencies [a1c6694]
465
+ - Updated dependencies [52af97e]
466
+ - Updated dependencies [a71422a]
467
+ - Updated dependencies [d6b5bc1]
468
+ - Updated dependencies [8fd9349]
469
+ - Updated dependencies [63e44f7]
470
+ - Updated dependencies [63b4885]
471
+ - Updated dependencies [38c4b62]
472
+ - Updated dependencies [6b74cd0]
473
+ - Updated dependencies [d86b161]
474
+ - @namzu/sdk@1.0.0
package/LICENSE.md ADDED
@@ -0,0 +1,110 @@
1
+ # Functional Source License, Version 1.1, MIT Future License
2
+
3
+ ## Abbreviation
4
+
5
+ FSL-1.1-MIT
6
+
7
+ ## Notice
8
+
9
+ Copyright 2026 Cogitave
10
+
11
+ ## Terms and Conditions
12
+
13
+ ### Licensor ("We")
14
+
15
+ The party offering the Software under these Terms and Conditions.
16
+
17
+ ### The Software
18
+
19
+ The "Software" is each version of the software that we make available under
20
+ these Terms and Conditions, as indicated by our inclusion of these Terms and
21
+ Conditions with the Software.
22
+
23
+ ### License Grant
24
+
25
+ Subject to your compliance with this License Grant and the Patents,
26
+ Redistribution and Trademark clauses below, we hereby grant you the right to
27
+ use, copy, modify, create derivative works, publicly perform, publicly display
28
+ and redistribute the Software for any Permitted Purpose identified below.
29
+
30
+ ### Permitted Purpose
31
+
32
+ A Permitted Purpose is any purpose other than a Competing Use. A Competing Use
33
+ means making the Software available to others in a commercial product or
34
+ service that:
35
+
36
+ 1. substitutes for the Software;
37
+
38
+ 2. substitutes for any other product or service we offer using the Software
39
+ that exists as of the date we make the Software available; or
40
+
41
+ 3. offers the same or substantially similar functionality as the Software.
42
+
43
+ Permitted Purposes specifically include using the Software:
44
+
45
+ 1. for your internal use and access;
46
+
47
+ 2. for non-commercial education;
48
+
49
+ 3. for non-commercial research; and
50
+
51
+ 4. in connection with professional services that you provide to a licensee
52
+ using the Software in accordance with these Terms and Conditions.
53
+
54
+ ### Patents
55
+
56
+ To the extent your use for a Permitted Purpose would necessarily infringe our
57
+ patents, the license grant above includes a license under our patents. If you
58
+ make a claim against any party that the Software infringes or contributes to
59
+ the infringement of any patent, then your patent license to the Software ends
60
+ immediately.
61
+
62
+ ### Redistribution
63
+
64
+ The Terms and Conditions apply to all copies, modifications and derivatives of
65
+ the Software.
66
+
67
+ If you redistribute any copies, modifications or derivatives of the Software,
68
+ you must include a copy of or a link to these Terms and Conditions and not
69
+ remove any copyright notices provided in or with the Software.
70
+
71
+ ### Disclaimer
72
+
73
+ THE SOFTWARE IS PROVIDED "AS IS" AND WITHOUT WARRANTIES OF ANY KIND, EXPRESS OR
74
+ IMPLIED, INCLUDING WITHOUT LIMITATION WARRANTIES OF FITNESS FOR A PARTICULAR
75
+ PURPOSE, MERCHANTABILITY, TITLE OR NON-INFRINGEMENT.
76
+
77
+ IN NO EVENT WILL WE HAVE ANY LIABILITY TO YOU ARISING OUT OF OR RELATED TO THE
78
+ SOFTWARE, INCLUDING INDIRECT, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES,
79
+ EVEN IF WE HAVE BEEN INFORMED OF THEIR POSSIBILITY IN ADVANCE.
80
+
81
+ ### Trademarks
82
+
83
+ Except for displaying the License Details and identifying us as the origin of
84
+ the Software, you have no right under these Terms and Conditions to use our
85
+ trademarks, trade names, service marks or product names.
86
+
87
+ ## Grant of Future License
88
+
89
+ We hereby irrevocably grant you an additional license to use the Software under
90
+ the MIT license that is effective on the second anniversary of the date we make
91
+ the Software available. On or after that date, you may use the Software under
92
+ the MIT license, in which case the following will apply:
93
+
94
+ Permission is hereby granted, free of charge, to any person obtaining a copy of
95
+ this software and associated documentation files (the "Software"), to deal in
96
+ the Software without restriction, including without limitation the rights to
97
+ use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
98
+ of the Software, and to permit persons to whom the Software is furnished to do
99
+ so, subject to the following conditions:
100
+
101
+ The above copyright notice and this permission notice shall be included in all
102
+ copies or substantial portions of the Software.
103
+
104
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
105
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
106
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
107
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
108
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
109
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
110
+ SOFTWARE.