@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.
- package/CHANGELOG.md +474 -0
- package/LICENSE.md +110 -0
- package/README.md +148 -0
- package/dist/backends/aci-standby-pool/index.d.ts +104 -0
- package/dist/backends/aci-standby-pool/index.d.ts.map +1 -0
- package/dist/backends/aci-standby-pool/index.js +425 -0
- package/dist/backends/aci-standby-pool/index.js.map +1 -0
- package/dist/backends/docker/__tests__/leaf-permissions.smoke.test.d.ts +40 -0
- package/dist/backends/docker/__tests__/leaf-permissions.smoke.test.d.ts.map +1 -0
- package/dist/backends/docker/__tests__/leaf-permissions.smoke.test.js +157 -0
- package/dist/backends/docker/__tests__/leaf-permissions.smoke.test.js.map +1 -0
- package/dist/backends/docker/index.d.ts +118 -0
- package/dist/backends/docker/index.d.ts.map +1 -0
- package/dist/backends/docker/index.js +645 -0
- package/dist/backends/docker/index.js.map +1 -0
- package/dist/backends/firecracker/__tests__/backend.test.d.ts +13 -0
- package/dist/backends/firecracker/__tests__/backend.test.d.ts.map +1 -0
- package/dist/backends/firecracker/__tests__/backend.test.js +353 -0
- package/dist/backends/firecracker/__tests__/backend.test.js.map +1 -0
- package/dist/backends/firecracker/__tests__/control-plane-mtls.test.d.ts +19 -0
- package/dist/backends/firecracker/__tests__/control-plane-mtls.test.d.ts.map +1 -0
- package/dist/backends/firecracker/__tests__/control-plane-mtls.test.js +201 -0
- package/dist/backends/firecracker/__tests__/control-plane-mtls.test.js.map +1 -0
- package/dist/backends/firecracker/__tests__/fixtures/mtls-pki.d.ts +39 -0
- package/dist/backends/firecracker/__tests__/fixtures/mtls-pki.d.ts.map +1 -0
- package/dist/backends/firecracker/__tests__/fixtures/mtls-pki.js +149 -0
- package/dist/backends/firecracker/__tests__/fixtures/mtls-pki.js.map +1 -0
- package/dist/backends/firecracker/__tests__/protocol.test.d.ts +6 -0
- package/dist/backends/firecracker/__tests__/protocol.test.d.ts.map +1 -0
- package/dist/backends/firecracker/__tests__/protocol.test.js +77 -0
- package/dist/backends/firecracker/__tests__/protocol.test.js.map +1 -0
- package/dist/backends/firecracker/__tests__/transport.test.d.ts +20 -0
- package/dist/backends/firecracker/__tests__/transport.test.d.ts.map +1 -0
- package/dist/backends/firecracker/__tests__/transport.test.js +449 -0
- package/dist/backends/firecracker/__tests__/transport.test.js.map +1 -0
- package/dist/backends/firecracker/index.d.ts +124 -0
- package/dist/backends/firecracker/index.d.ts.map +1 -0
- package/dist/backends/firecracker/index.js +334 -0
- package/dist/backends/firecracker/index.js.map +1 -0
- package/dist/backends/firecracker/protocol.d.ts +132 -0
- package/dist/backends/firecracker/protocol.d.ts.map +1 -0
- package/dist/backends/firecracker/protocol.js +112 -0
- package/dist/backends/firecracker/protocol.js.map +1 -0
- package/dist/backends/firecracker/transport.d.ts +251 -0
- package/dist/backends/firecracker/transport.d.ts.map +1 -0
- package/dist/backends/firecracker/transport.js +524 -0
- package/dist/backends/firecracker/transport.js.map +1 -0
- package/dist/index.d.ts +611 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +376 -0
- package/dist/index.js.map +1 -0
- package/dist/index.test.d.ts +28 -0
- package/dist/index.test.d.ts.map +1 -0
- package/dist/index.test.js +670 -0
- package/dist/index.test.js.map +1 -0
- package/package.json +54 -0
- package/src/backends/aci-standby-pool/index.ts +602 -0
- package/src/backends/docker/__tests__/leaf-permissions.smoke.test.ts +169 -0
- package/src/backends/docker/index.ts +826 -0
- package/src/backends/firecracker/__tests__/backend.test.ts +418 -0
- package/src/backends/firecracker/__tests__/control-plane-mtls.test.ts +253 -0
- package/src/backends/firecracker/__tests__/fixtures/mtls-pki.ts +166 -0
- package/src/backends/firecracker/__tests__/protocol.test.ts +90 -0
- package/src/backends/firecracker/__tests__/transport.test.ts +526 -0
- package/src/backends/firecracker/index.ts +528 -0
- package/src/backends/firecracker/protocol.ts +191 -0
- package/src/backends/firecracker/transport.ts +667 -0
- package/src/index.test.ts +731 -0
- 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.
|