@torkbot/sandbox 0.3.0 → 0.4.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/README.md CHANGED
@@ -17,7 +17,7 @@ const workspaceFs = fs.memory({
17
17
  });
18
18
 
19
19
  const sandbox = defineSandbox({
20
- rootfs: rootfs.builtIn("alpine:3.20"),
20
+ rootfs: rootfs.builtIn("alpine:3.23"),
21
21
  resources: {
22
22
  cpus: 2,
23
23
  memoryMiB: 2048,
@@ -53,7 +53,7 @@ import {
53
53
  const workspaceFs = fs.memory();
54
54
 
55
55
  const sandbox = defineSandbox({
56
- rootfs: rootfs.builtIn("alpine:3.20"),
56
+ rootfs: rootfs.builtIn("alpine:3.23"),
57
57
  });
58
58
 
59
59
  await using lane = await sandbox.boot({
@@ -79,10 +79,68 @@ The public API is split into three layers:
79
79
  - `lane.exec(...)` runs buffered work inside the booted instance.
80
80
 
81
81
  Expensive artifact preparation is intentionally outside `boot()`.
82
- `rootfs.builtIn("alpine:3.20")` selects a built-in rootfs artifact that must
82
+ `rootfs.builtIn("alpine:3.23")` selects a built-in rootfs artifact that must
83
83
  already be installed with Sandbox. It does not pull an image or build a rootfs
84
84
  at runtime.
85
85
 
86
+ ## Durable, Policy-Controlled Instances
87
+
88
+ Sandbox composes durable rootfs mutation with explicit network policy. In this
89
+ example, dirty COW blocks are synchronized to blob storage, public HTTP(S)
90
+ egress is allowed, and only GitHub API requests receive an installation token:
91
+
92
+ ```ts
93
+ import {
94
+ defineSandbox,
95
+ network,
96
+ rootfs,
97
+ type SandboxBlockStore,
98
+ } from "@torkbot/sandbox";
99
+
100
+ const writableRootfs: SandboxBlockStore = new BlobSynchronizedCowBlockStore({
101
+ bucket: "sandbox-rootfs-overlays",
102
+ keyPrefix: "lanes/github-worker",
103
+ });
104
+
105
+ const githubTokens = new GitHubInstallationTokenService({
106
+ installationId: 123456,
107
+ });
108
+
109
+ const sandbox = defineSandbox({
110
+ rootfs: rootfs.cow({
111
+ base: rootfs.builtIn("alpine:3.23"),
112
+ writable: writableRootfs,
113
+ }),
114
+ resources: {
115
+ cpus: 4,
116
+ memoryMiB: 4096,
117
+ },
118
+ network: network.policy(async (conn) => {
119
+ if (conn.transport === "udp" && conn.dst.port === 53) {
120
+ conn.accept();
121
+ return;
122
+ }
123
+ if (conn.transport === "tcp" && conn.dst.port === 443) {
124
+ conn.acceptHttp(async (request) => {
125
+ if (request.destination.hostname !== "api.github.com") return;
126
+ request.headers.set(
127
+ "authorization",
128
+ `Bearer ${await githubTokens.tokenForRequest(request)}`,
129
+ );
130
+ });
131
+ return;
132
+ }
133
+ }),
134
+ });
135
+
136
+ await using lane = await sandbox.boot({ cwd: "/workspace" });
137
+
138
+ await lane.exec("sh", [
139
+ "-lc",
140
+ "apk add --no-cache git curl && curl -fsSL https://api.github.com/user",
141
+ ]);
142
+ ```
143
+
86
144
  ## API Overview
87
145
 
88
146
  ### Configuration
@@ -102,7 +160,7 @@ type SandboxDefinition = {
102
160
  the read-only built-in catalog:
103
161
 
104
162
  ```ts
105
- rootfs.builtIn("alpine:3.20");
163
+ rootfs.builtIn("alpine:3.23");
106
164
  ```
107
165
 
108
166
  `resources` controls the VM shape used by every instance booted from the
@@ -110,7 +168,7 @@ definition. Omitted values use Sandbox defaults.
110
168
 
111
169
  ```ts
112
170
  defineSandbox({
113
- rootfs: rootfs.builtIn("alpine:3.20"),
171
+ rootfs: rootfs.builtIn("alpine:3.23"),
114
172
  resources: {
115
173
  cpus: 4,
116
174
  memoryMiB: 4096,
@@ -120,14 +178,15 @@ defineSandbox({
120
178
 
121
179
  Use `rootfs.cow(...)` when rootfs mutations should persist. The sandbox library
122
180
  owns the COW block-device contract; user-space owns the block store's
123
- durability, compression, migration, and checkpoint policy. Built-in rootfs
124
- packages include a read-only EROFS image for normal boots and a writable ext4
125
- image used as the COW base.
181
+ durability, migration, and checkpoint policy. Built-in rootfs packages include
182
+ one compressed QCOW2 image with an ext4 guest filesystem. `rootfs.builtIn(...)`
183
+ mounts that image read-only in the guest; `rootfs.cow(...)` mounts the same base
184
+ read-write through the host COW block store.
126
185
 
127
186
  ```ts
128
187
  defineSandbox({
129
188
  rootfs: rootfs.cow({
130
- base: rootfs.builtIn("alpine:3.20"),
189
+ base: rootfs.builtIn("alpine:3.23"),
131
190
  writable: laneBlockStore,
132
191
  }),
133
192
  });
@@ -169,33 +228,65 @@ connection requests and grants only the traffic it explicitly allows:
169
228
 
170
229
  ```ts
171
230
  const policy = network.policy(async (conn) => {
172
- if (conn.host === "registry.npmjs.org") {
173
- conn.allowHttp();
231
+ if (conn.transport === "tcp" && conn.dst.isPublicInternet() && conn.dst.port === 443) {
232
+ conn.accept();
174
233
  }
175
234
  });
176
235
  ```
177
236
 
178
- `conn.allow()` grants HTTP(S)-classified traffic without request middleware.
179
- `conn.allowHttp(...)` grants HTTP(S)-classified traffic and can apply request
180
- middleware:
237
+ `conn.accept()` grants the observed connection or flow at the transport layer.
238
+ It does not classify the application protocol, does not enter HTTP middleware,
239
+ and does not MITM TLS. `conn.acceptHttp(...)` is TCP-only and explicitly opts
240
+ the flow into Sandbox's HTTP-family enforcement path. If the accepted flow is
241
+ not actually HTTP or HTTPS, it fails closed.
181
242
 
182
243
  ```ts
183
244
  const policy = network.policy(async (conn) => {
184
- if (conn.host !== "api.example.com") return;
185
-
186
- conn.allowHttp(async (request) => {
187
- request.headers.set(
188
- "authorization",
189
- `Bearer ${await credentialBroker.authorizationFor(request)}`,
190
- );
191
- });
245
+ if (
246
+ conn.transport === "tcp" &&
247
+ conn.dst.isPublicInternet() &&
248
+ conn.dst.port === 443
249
+ ) {
250
+ conn.acceptHttp(async (request) => {
251
+ if (request.destination.hostname !== "api.example.com") return;
252
+ request.headers.set(
253
+ "authorization",
254
+ `Bearer ${await credentialBroker.authorizationFor(request)}`,
255
+ );
256
+ });
257
+ }
192
258
  });
193
259
  ```
194
260
 
261
+ Every TCP and UDP policy request carries source and destination IP-layer
262
+ endpoints:
263
+
264
+ ```ts
265
+ conn.src.ip;
266
+ conn.src.port;
267
+ conn.dst.ip;
268
+ conn.dst.port;
269
+ ```
270
+
271
+ Endpoint helpers classify logical address ranges without relying on hostnames:
272
+ `isLoopback()`, `isPrivate()`, `isLinkLocal()`, `isMulticast()`,
273
+ `isBroadcast()`, `isDocumentation()`, `isReserved()`, and
274
+ `isPublicInternet()`. `transport` is the TCP/UDP discriminator. TCP callbacks
275
+ may also include `conn.signals` metadata such as TLS SNI or ALPN when the
276
+ runtime has explicitly observed it, but Sandbox does not expose a best-effort
277
+ `conn.protocol` classifier.
278
+
279
+ HTTP middleware receives trusted destination metadata separately from the
280
+ request URL. `request.destination.hostname` is populated only when Sandbox can
281
+ pin the destination IP to trusted connection metadata, such as its own DNS
282
+ answer cache. IP-addressed requests still work under `acceptHttp(...)`, but they
283
+ do not advertise a hostname. Do not use the HTTP `Host` header as authority for
284
+ policy decisions.
285
+
195
286
  Deny remains the default. If the policy callback does not create a grant, the
196
- connection is blocked. The `NetworkGrant` returned by `allow()` and
197
- `allowHttp()` is reserved as the future extension point for instance-local
198
- state, such as remembering a grant for a time window.
287
+ connection is blocked. The grants returned by `accept()` and `acceptHttp()` are
288
+ reserved as future extension points for instance-local state, such as
289
+ remembering a grant for a time window.
199
290
 
200
291
  The runtime uses this policy shape to keep the JavaScript boundary explicit.
201
292
  Native rules can be added under the same model later without changing the
@@ -262,7 +353,7 @@ Sandbox hides the kernel, init, transport, and host helper behind a small
262
353
  TypeScript API:
263
354
 
264
355
  - The runtime boots a libkrun-backed guest from a prebuilt rootfs artifact:
265
- read-only EROFS by default, or writable ext4 when a COW rootfs is used.
356
+ a compressed QCOW2 image that contains an ext4 guest filesystem.
266
357
  - Kernel and init artifacts are implementation details owned by Sandbox.
267
358
  - A signed `sandbox-host` helper owns the Node/Rust/libkrun boundary.
268
359
  - Guest control traffic uses an implicit fd-backed transport between the host
@@ -274,6 +365,14 @@ TypeScript API:
274
365
  decisions and delegate to JavaScript only when a policy callback is required.
275
366
  - HTTP request middleware is caller-provided JavaScript, but Sandbox owns the
276
367
  interception machinery and certificate plumbing.
368
+ - When HTTP interception is enabled, the host generates the CA material and
369
+ passes only the public CA certificate to Sandbox init. Init does not generate
370
+ or manage certificates; the host exposes the supplied CA through an internal
371
+ read-only virtiofs mount, then init installs it using the selected rootfs'
372
+ native trust-store mechanism. Built-in rootfs launches use an ephemeral
373
+ writable COW view for HTTP interception so init can update the guest trust
374
+ store deterministically. If a rootfs does not provide a supported native
375
+ trust-store installer, init fails closed.
277
376
 
278
377
  The intended boundary is that Sandbox knows how to launch, isolate, mount,
279
378
  intercept, and enforce. User-space owns artifact selection, filesystem
@@ -1,16 +1,15 @@
1
1
  type SandboxTarget = {
2
2
  readonly packageName: string;
3
3
  readonly hostBinaryName: string;
4
- readonly rootfsNames: Record<BuiltInRootfsFormat, string>;
4
+ readonly rootfsName: string;
5
5
  readonly platform: NodeJS.Platform;
6
6
  readonly arch: NodeJS.Architecture;
7
7
  readonly libc?: "glibc";
8
8
  };
9
- type BuiltInRootfsFormat = "erofs" | "ext4";
10
9
  export declare function currentSandboxTarget(): SandboxTarget;
11
10
  export declare function hostBinaryPath(): string;
12
- export declare function builtInRootfsPath(name: "alpine:3.20", format?: BuiltInRootfsFormat): string;
13
- export declare function builtInRootfsIdentity(name: "alpine:3.20", format: BuiltInRootfsFormat): string;
11
+ export declare function builtInRootfsPath(name: "alpine:3.23"): string;
12
+ export declare function builtInRootfsIdentity(name: "alpine:3.23"): string;
14
13
  export declare function rawHostBinaryPath(): string;
15
14
  export declare function assertMacosHostIsSigned(path: string): void;
16
15
  export declare function macosHostSigningError(path: string): Error | null;
@@ -1 +1 @@
1
- {"version":3,"file":"artifacts.d.ts","sourceRoot":"","sources":["../src/artifacts.ts"],"names":[],"mappings":"AAKA,KAAK,aAAa,GAAG;IACnB,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;IAC7B,QAAQ,CAAC,cAAc,EAAE,MAAM,CAAC;IAChC,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC,mBAAmB,EAAE,MAAM,CAAC,CAAC;IAC1D,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC,QAAQ,CAAC;IACnC,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC,YAAY,CAAC;IACnC,QAAQ,CAAC,IAAI,CAAC,EAAE,OAAO,CAAC;CACzB,CAAC;AAEF,KAAK,mBAAmB,GAAG,OAAO,GAAG,MAAM,CAAC;AA0B5C,wBAAgB,oBAAoB,IAAI,aAAa,CAYpD;AAED,wBAAgB,cAAc,IAAI,MAAM,CAEvC;AAED,wBAAgB,iBAAiB,CAAC,IAAI,EAAE,aAAa,EAAE,MAAM,GAAE,mBAA6B,GAAG,MAAM,CAMpG;AAED,wBAAgB,qBAAqB,CAAC,IAAI,EAAE,aAAa,EAAE,MAAM,EAAE,mBAAmB,GAAG,MAAM,CAiB9F;AAED,wBAAgB,iBAAiB,IAAI,MAAM,CAG1C;AA2BD,wBAAgB,uBAAuB,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI,CAuB1D;AAcD,wBAAgB,qBAAqB,CAAC,IAAI,EAAE,MAAM,GAAG,KAAK,GAAG,IAAI,CAWhE"}
1
+ {"version":3,"file":"artifacts.d.ts","sourceRoot":"","sources":["../src/artifacts.ts"],"names":[],"mappings":"AAKA,KAAK,aAAa,GAAG;IACnB,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;IAC7B,QAAQ,CAAC,cAAc,EAAE,MAAM,CAAC;IAChC,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;IAC5B,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC,QAAQ,CAAC;IACnC,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC,YAAY,CAAC;IACnC,QAAQ,CAAC,IAAI,CAAC,EAAE,OAAO,CAAC;CACzB,CAAC;AAoBF,wBAAgB,oBAAoB,IAAI,aAAa,CAYpD;AAED,wBAAgB,cAAc,IAAI,MAAM,CAEvC;AAED,wBAAgB,iBAAiB,CAAC,IAAI,EAAE,aAAa,GAAG,MAAM,CAM7D;AAED,wBAAgB,qBAAqB,CAAC,IAAI,EAAE,aAAa,GAAG,MAAM,CAiBjE;AAED,wBAAgB,iBAAiB,IAAI,MAAM,CAG1C;AA2BD,wBAAgB,uBAAuB,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI,CAuB1D;AAcD,wBAAgB,qBAAqB,CAAC,IAAI,EAAE,MAAM,GAAG,KAAK,GAAG,IAAI,CAWhE"}
package/dist/artifacts.js CHANGED
@@ -5,20 +5,14 @@ const targets = [
5
5
  {
6
6
  packageName: "@torkbot/sandbox-darwin-arm64",
7
7
  hostBinaryName: "sandbox-host",
8
- rootfsNames: {
9
- erofs: "rootfs/alpine-3.20.erofs",
10
- ext4: "rootfs/alpine-3.20.ext4",
11
- },
8
+ rootfsName: "rootfs/alpine-3.23.qcow2",
12
9
  platform: "darwin",
13
10
  arch: "arm64",
14
11
  },
15
12
  {
16
13
  packageName: "@torkbot/sandbox-linux-x64-gnu",
17
14
  hostBinaryName: "sandbox-host",
18
- rootfsNames: {
19
- erofs: "rootfs/alpine-3.20.erofs",
20
- ext4: "rootfs/alpine-3.20.ext4",
21
- },
15
+ rootfsName: "rootfs/alpine-3.23.qcow2",
22
16
  platform: "linux",
23
17
  arch: "x64",
24
18
  libc: "glibc",
@@ -36,27 +30,27 @@ export function currentSandboxTarget() {
36
30
  export function hostBinaryPath() {
37
31
  return rawHostBinaryPath();
38
32
  }
39
- export function builtInRootfsPath(name, format = "erofs") {
40
- if (name === "alpine:3.20") {
33
+ export function builtInRootfsPath(name) {
34
+ if (name === "alpine:3.23") {
41
35
  const target = currentSandboxTarget();
42
- return resolveArtifactPath(target, target.rootfsNames[format]);
36
+ return resolveArtifactPath(target, target.rootfsName);
43
37
  }
44
38
  throw new Error(`unsupported built-in rootfs: ${name}`);
45
39
  }
46
- export function builtInRootfsIdentity(name, format) {
47
- if (name === "alpine:3.20") {
40
+ export function builtInRootfsIdentity(name) {
41
+ if (name === "alpine:3.23") {
48
42
  const target = currentSandboxTarget();
49
43
  const packageVersion = platformPackageVersion(target);
50
44
  return [
51
45
  "built-in",
52
46
  name,
53
- format,
47
+ "qcow2",
54
48
  target.platform,
55
49
  target.arch,
56
50
  target.libc ?? "none",
57
51
  target.packageName,
58
52
  packageVersion,
59
- target.rootfsNames[format],
53
+ target.rootfsName,
60
54
  ].join(":");
61
55
  }
62
56
  throw new Error(`unsupported built-in rootfs: ${name}`);
@@ -1 +1 @@
1
- {"version":3,"file":"artifacts.js","sourceRoot":"","sources":["../src/artifacts.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"artifacts.js","sourceRoot":"","sources":["../src/artifacts.ts"],"names":[],"mappings":"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"}
@@ -1,13 +1,13 @@
1
1
  import { hostBinaryPath } from "./artifacts.ts";
2
2
  import type { HostControlChannel } from "./control.ts";
3
3
  import type { HostSpawnSandboxOptions } from "./spawn-options.ts";
4
- import type { InternalSandboxOptions, RegisteredHttpRequestHeadersHook } from "./launch-options.ts";
4
+ import type { InternalSandboxOptions, RegisteredHttpRequestHeadersHook, RegisteredNetworkConnectionHook } from "./launch-options.ts";
5
5
  export declare class HostProcessSandboxVm implements HostControlChannel {
6
6
  #private;
7
7
  readonly hasControlSocket = true;
8
8
  readonly packets: AsyncIterable<Uint8Array>;
9
9
  private constructor();
10
- static spawn(options: InternalSandboxOptions, hostOptions: HostSpawnSandboxOptions, requestHeaderHooks?: Map<string, RegisteredHttpRequestHeadersHook>): Promise<HostProcessSandboxVm>;
10
+ static spawn(options: InternalSandboxOptions, hostOptions: HostSpawnSandboxOptions, requestHeaderHooks?: Map<string, RegisteredHttpRequestHeadersHook>, networkConnectionHook?: RegisteredNetworkConnectionHook): Promise<HostProcessSandboxVm>;
11
11
  writeControlPacket(packet: Uint8Array): void;
12
12
  close(): Promise<void>;
13
13
  terminateHostForTest(): Promise<void>;
@@ -1 +1 @@
1
- {"version":3,"file":"host-process.d.ts","sourceRoot":"","sources":["../src/host-process.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,cAAc,EAAyB,MAAM,gBAAgB,CAAC;AACvE,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,cAAc,CAAC;AACvD,OAAO,KAAK,EAAE,uBAAuB,EAAE,MAAM,oBAAoB,CAAC;AAQlE,OAAO,KAAK,EACV,sBAAsB,EACtB,gCAAgC,EACjC,MAAM,qBAAqB,CAAC;AAI7B,qBAAa,oBAAqB,YAAW,kBAAkB;;IAC7D,QAAQ,CAAC,gBAAgB,QAAQ;IACjC,QAAQ,CAAC,OAAO,EAAE,aAAa,CAAC,UAAU,CAAC,CAAC;IAiB5C,OAAO,eA6CN;IAED,OAAa,KAAK,CAChB,OAAO,EAAE,sBAAsB,EAC/B,WAAW,EAAE,uBAAuB,EACpC,kBAAkB,GAAE,GAAG,CAAC,MAAM,EAAE,gCAAgC,CAAa,GAC5E,OAAO,CAAC,oBAAoB,CAAC,CAyB/B;IAED,kBAAkB,CAAC,MAAM,EAAE,UAAU,GAAG,IAAI,CAG3C;IAEK,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC,CA4B3B;IAEK,oBAAoB,IAAI,OAAO,CAAC,IAAI,CAAC,CAiB1C;CAqhBF;AA2BD,OAAO,EAAE,cAAc,EAAE,CAAC"}
1
+ {"version":3,"file":"host-process.d.ts","sourceRoot":"","sources":["../src/host-process.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,cAAc,EAAyB,MAAM,gBAAgB,CAAC;AACvE,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,cAAc,CAAC;AACvD,OAAO,KAAK,EAAE,uBAAuB,EAAE,MAAM,oBAAoB,CAAC;AAQlE,OAAO,KAAK,EACV,sBAAsB,EACtB,gCAAgC,EAChC,+BAA+B,EAChC,MAAM,qBAAqB,CAAC;AAU7B,qBAAa,oBAAqB,YAAW,kBAAkB;;IAC7D,QAAQ,CAAC,gBAAgB,QAAQ;IACjC,QAAQ,CAAC,OAAO,EAAE,aAAa,CAAC,UAAU,CAAC,CAAC;IAmB5C,OAAO,eAkDN;IAED,OAAa,KAAK,CAChB,OAAO,EAAE,sBAAsB,EAC/B,WAAW,EAAE,uBAAuB,EACpC,kBAAkB,GAAE,GAAG,CAAC,MAAM,EAAE,gCAAgC,CAAa,EAC7E,qBAAqB,CAAC,EAAE,+BAA+B,GACtD,OAAO,CAAC,oBAAoB,CAAC,CAyB/B;IAED,kBAAkB,CAAC,MAAM,EAAE,UAAU,GAAG,IAAI,CAG3C;IAEK,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC,CA4B3B;IAEK,oBAAoB,IAAI,OAAO,CAAC,IAAI,CAAC,CAiB1C;CAqnBF;AA2BD,OAAO,EAAE,cAAc,EAAE,CAAC"}
@@ -16,16 +16,19 @@ export class HostProcessSandboxVm {
16
16
  #rootBlockStore;
17
17
  #rootBlockStoreContext;
18
18
  #requestHeaderHooks;
19
+ #networkConnectionHook;
20
+ #httpMiddlewareByFlow = new Map();
19
21
  #buffer = new Uint8Array();
20
22
  #stderr = "";
21
23
  #closed = false;
22
24
  #exitError = null;
23
25
  #stdinError = null;
24
- constructor(child, options, requestHeaderHooks) {
26
+ constructor(child, options, requestHeaderHooks, networkConnectionHook) {
25
27
  this.#child = child;
26
28
  this.#options = options;
27
29
  this.packets = this.#packets;
28
30
  this.#requestHeaderHooks = requestHeaderHooks;
31
+ this.#networkConnectionHook = networkConnectionHook;
29
32
  this.#rootBlockStore = options.rootfs.storage?.blockStore;
30
33
  this.#rootBlockStoreContext = options.rootfs.storage?.context;
31
34
  for (const mount of options.mounts ?? []) {
@@ -38,6 +41,9 @@ export class HostProcessSandboxVm {
38
41
  const text = chunk.toString("utf8").trim();
39
42
  if (text.length > 0) {
40
43
  this.#stderr = this.#stderr.length === 0 ? text : `${this.#stderr}\n${text}`;
44
+ if (process.env.SANDBOX_HOST_STDERR === "1") {
45
+ process.stderr.write(chunk);
46
+ }
41
47
  }
42
48
  });
43
49
  child.stdin.on("error", (error) => {
@@ -59,14 +65,14 @@ export class HostProcessSandboxVm {
59
65
  this.#launchReady.close(this.#exitError);
60
66
  });
61
67
  }
62
- static async spawn(options, hostOptions, requestHeaderHooks = new Map()) {
68
+ static async spawn(options, hostOptions, requestHeaderHooks = new Map(), networkConnectionHook) {
63
69
  let vm;
64
70
  const hostPath = hostBinaryPath();
65
71
  try {
66
72
  const child = spawn(hostPath, ["--stdio"], {
67
73
  stdio: ["pipe", "pipe", "pipe"],
68
74
  });
69
- vm = new HostProcessSandboxVm(child, options, requestHeaderHooks);
75
+ vm = new HostProcessSandboxVm(child, options, requestHeaderHooks, networkConnectionHook);
70
76
  await Promise.race([
71
77
  once(child, "spawn"),
72
78
  once(child, "error").then(([error]) => {
@@ -222,6 +228,8 @@ export class HostProcessSandboxVm {
222
228
  && type !== "host.vfs.removexattr"
223
229
  && type !== "host.http.requestHeaders"
224
230
  && type !== "host.http.activeRequestHeaderHooks"
231
+ && type !== "host.network.connection"
232
+ && type !== "host.network.closed"
225
233
  && type !== "host.block.list"
226
234
  && type !== "host.block.read"
227
235
  && type !== "host.block.write"
@@ -234,6 +242,12 @@ export class HostProcessSandboxVm {
234
242
  else if (type === "host.http.activeRequestHeaderHooks") {
235
243
  void this.#handleActiveRequestHeaderHooks(document);
236
244
  }
245
+ else if (type === "host.network.connection") {
246
+ void this.#handleNetworkConnection(document);
247
+ }
248
+ else if (type === "host.network.closed") {
249
+ this.#handleNetworkClosed(document);
250
+ }
237
251
  else if (type === "host.block.list"
238
252
  || type === "host.block.read"
239
253
  || type === "host.block.write"
@@ -245,6 +259,85 @@ export class HostProcessSandboxVm {
245
259
  }
246
260
  return true;
247
261
  }
262
+ async #handleNetworkConnection(document) {
263
+ const id = typeof document.id === "string" ? document.id : "";
264
+ try {
265
+ const transport = assertNetworkTransport(document.transport);
266
+ const srcIp = assertString(document.srcIp, "srcIp");
267
+ const srcPort = assertNumber(document.srcPort, "srcPort");
268
+ const dstIp = assertString(document.dstIp, "dstIp");
269
+ const dstPort = assertNumber(document.dstPort, "dstPort");
270
+ const src = createNetworkEndpoint(srcIp, srcPort);
271
+ const dst = createNetworkEndpoint(dstIp, dstPort);
272
+ const decision = { action: "deny" };
273
+ let httpMiddleware;
274
+ const accept = () => {
275
+ decision.action = "accept";
276
+ return {};
277
+ };
278
+ const connection = {
279
+ transport,
280
+ src,
281
+ dst,
282
+ accept,
283
+ ...(transport === "tcp"
284
+ ? {
285
+ acceptHttp(middleware) {
286
+ decision.action = "acceptHttp";
287
+ httpMiddleware = middleware;
288
+ return {};
289
+ },
290
+ }
291
+ : {}),
292
+ };
293
+ if (this.#networkConnectionHook?.active === true) {
294
+ await this.#networkConnectionHook.hook(connection);
295
+ }
296
+ if (decision.action === "acceptHttp") {
297
+ this.#httpMiddlewareByFlow.set(networkFlowKey(src, dst), httpMiddleware);
298
+ }
299
+ this.#tryWriteToHost(encodePacket({
300
+ type: "host.network.response",
301
+ id,
302
+ ok: true,
303
+ action: decision.action,
304
+ }));
305
+ }
306
+ catch (error) {
307
+ this.#tryWriteToHost(encodePacket({
308
+ type: "host.network.response",
309
+ id,
310
+ ok: false,
311
+ error: error instanceof Error ? error.message : String(error),
312
+ }));
313
+ }
314
+ }
315
+ #handleNetworkClosed(document) {
316
+ const id = typeof document.id === "string" ? document.id : "";
317
+ try {
318
+ const transport = assertNetworkTransport(document.transport);
319
+ const srcIp = assertString(document.srcIp, "srcIp");
320
+ const srcPort = assertNumber(document.srcPort, "srcPort");
321
+ const dstIp = assertString(document.dstIp, "dstIp");
322
+ const dstPort = assertNumber(document.dstPort, "dstPort");
323
+ if (transport === "tcp") {
324
+ this.#httpMiddlewareByFlow.delete(networkFlowKey(createNetworkEndpoint(srcIp, srcPort), createNetworkEndpoint(dstIp, dstPort)));
325
+ }
326
+ this.#tryWriteToHost(encodePacket({
327
+ type: "host.network.response",
328
+ id,
329
+ ok: true,
330
+ }));
331
+ }
332
+ catch (error) {
333
+ this.#tryWriteToHost(encodePacket({
334
+ type: "host.network.response",
335
+ id,
336
+ ok: false,
337
+ error: error instanceof Error ? error.message : String(error),
338
+ }));
339
+ }
340
+ }
248
341
  async #handleHttpRequestHeaders(document) {
249
342
  const id = typeof document.id === "string" ? document.id : "";
250
343
  try {
@@ -258,13 +351,15 @@ export class HostProcessSandboxVm {
258
351
  method: assertString(document.method, "method"),
259
352
  headers,
260
353
  destination: {
354
+ sourceIp: assertString(document.sourceIp, "sourceIp"),
355
+ sourcePort: assertNumber(document.sourcePort, "sourcePort"),
261
356
  originalIp: assertString(document.originalDestinationIp, "originalDestinationIp"),
262
357
  originalPort: assertNumber(document.originalDestinationPort, "originalDestinationPort"),
263
- upstreamIp: assertString(document.upstreamDialIp, "upstreamDialIp"),
264
- upstreamPort: assertNumber(document.upstreamDialPort, "upstreamDialPort"),
358
+ hostname: optionalString(document.originalDestinationHostname, "originalDestinationHostname"),
265
359
  },
266
360
  tls: optionalTlsMetadata(document.tls),
267
361
  };
362
+ await this.#httpMiddlewareByFlow.get(networkFlowKey(createNetworkEndpoint(request.destination.sourceIp, request.destination.sourcePort), createNetworkEndpoint(request.destination.originalIp, request.destination.originalPort)))?.(request);
268
363
  for (const hookId of hookIds) {
269
364
  const registration = this.#requestHeaderHooks.get(hookId);
270
365
  if (registration?.active === true) {
@@ -743,6 +838,108 @@ function assertProtocol(value) {
743
838
  }
744
839
  throw new Error("host request protocol must be http/1.1 or h2");
745
840
  }
841
+ function assertNetworkTransport(value) {
842
+ if (value === "tcp" || value === "udp") {
843
+ return value;
844
+ }
845
+ throw new Error("host network request transport must be tcp or udp");
846
+ }
847
+ function createNetworkEndpoint(ip, port) {
848
+ return {
849
+ ip,
850
+ port,
851
+ isLoopback: () => isLoopbackIp(ip),
852
+ isPrivate: () => isPrivateIp(ip),
853
+ isLinkLocal: () => isLinkLocalIp(ip),
854
+ isMulticast: () => isMulticastIp(ip),
855
+ isBroadcast: () => ip === "255.255.255.255",
856
+ isDocumentation: () => isDocumentationIp(ip),
857
+ isReserved: () => isReservedIp(ip),
858
+ isPublicInternet: () => isPublicInternetIp(ip),
859
+ };
860
+ }
861
+ function networkFlowKey(src, dst) {
862
+ return `${src.ip}:${src.port}->${dst.ip}:${dst.port}`;
863
+ }
864
+ function isLoopbackIp(ip) {
865
+ const ipv4 = parseIpv4(ip);
866
+ if (ipv4 !== undefined) {
867
+ return ipv4[0] === 127;
868
+ }
869
+ return ip.toLowerCase() === "::1";
870
+ }
871
+ function isPrivateIp(ip) {
872
+ const ipv4 = parseIpv4(ip);
873
+ if (ipv4 !== undefined) {
874
+ return ipv4[0] === 10
875
+ || (ipv4[0] === 172 && ipv4[1] >= 16 && ipv4[1] <= 31)
876
+ || (ipv4[0] === 192 && ipv4[1] === 168);
877
+ }
878
+ const lower = ip.toLowerCase();
879
+ return lower.startsWith("fc") || lower.startsWith("fd");
880
+ }
881
+ function isLinkLocalIp(ip) {
882
+ const ipv4 = parseIpv4(ip);
883
+ if (ipv4 !== undefined) {
884
+ return ipv4[0] === 169 && ipv4[1] === 254;
885
+ }
886
+ return /^fe[89ab]/i.test(ip);
887
+ }
888
+ function isMulticastIp(ip) {
889
+ const ipv4 = parseIpv4(ip);
890
+ if (ipv4 !== undefined) {
891
+ return ipv4[0] >= 224 && ipv4[0] <= 239;
892
+ }
893
+ return ip.toLowerCase().startsWith("ff");
894
+ }
895
+ function isDocumentationIp(ip) {
896
+ const ipv4 = parseIpv4(ip);
897
+ if (ipv4 !== undefined) {
898
+ return (ipv4[0] === 192 && ipv4[1] === 0 && ipv4[2] === 2)
899
+ || (ipv4[0] === 198 && ipv4[1] === 51 && ipv4[2] === 100)
900
+ || (ipv4[0] === 203 && ipv4[1] === 0 && ipv4[2] === 113);
901
+ }
902
+ return ip.toLowerCase().startsWith("2001:db8:");
903
+ }
904
+ function isReservedIp(ip) {
905
+ const ipv4 = parseIpv4(ip);
906
+ if (ipv4 !== undefined) {
907
+ return ipv4[0] === 0
908
+ || (ipv4[0] === 100 && ipv4[1] >= 64 && ipv4[1] <= 127)
909
+ || (ipv4[0] === 192 && ipv4[1] === 0 && ipv4[2] === 0)
910
+ || (ipv4[0] === 192 && ipv4[1] === 88 && ipv4[2] === 99)
911
+ || (ipv4[0] === 198 && (ipv4[1] === 18 || ipv4[1] === 19))
912
+ || ipv4[0] >= 240
913
+ || isDocumentationIp(ip);
914
+ }
915
+ return ip.toLowerCase().startsWith("2001:db8:");
916
+ }
917
+ function isPublicInternetIp(ip) {
918
+ return parseIpv4(ip) !== undefined
919
+ && !isLoopbackIp(ip)
920
+ && !isPrivateIp(ip)
921
+ && !isLinkLocalIp(ip)
922
+ && !isMulticastIp(ip)
923
+ && !isReservedIp(ip)
924
+ && !isDocumentationIp(ip)
925
+ && ip !== "255.255.255.255";
926
+ }
927
+ function parseIpv4(ip) {
928
+ const parts = ip.split(".");
929
+ if (parts.length !== 4) {
930
+ return undefined;
931
+ }
932
+ const octets = parts.map((part) => {
933
+ if (!/^(0|[1-9][0-9]{0,2})$/.test(part)) {
934
+ return undefined;
935
+ }
936
+ const value = Number(part);
937
+ return value <= 255 ? value : undefined;
938
+ });
939
+ return octets.every((part) => part !== undefined)
940
+ ? octets
941
+ : undefined;
942
+ }
746
943
  function optionalTlsMetadata(value) {
747
944
  if (value === undefined || value === null) {
748
945
  return undefined;
@@ -760,6 +957,9 @@ function optionalTlsMetadata(value) {
760
957
  : assertString(document.alpn, "tls.alpn"),
761
958
  };
762
959
  }
960
+ function optionalString(value, field) {
961
+ return value === undefined || value === null ? undefined : assertString(value, field);
962
+ }
763
963
  function binaryField(value, field) {
764
964
  if (value instanceof Uint8Array) {
765
965
  return value;
@@ -922,6 +1122,7 @@ function encodeHostSpawn(options) {
922
1122
  mounts: options.mounts ?? [],
923
1123
  networkOutbound: options.network?.outbound,
924
1124
  networkHttp: options.network?.http === undefined ? undefined : options.network.http,
1125
+ networkPolicy: options.network?.policy === undefined ? undefined : options.network.policy,
925
1126
  });
926
1127
  }
927
1128
  function encodePacket(document) {