@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 +125 -26
- package/dist/artifacts.d.ts +3 -4
- package/dist/artifacts.d.ts.map +1 -1
- package/dist/artifacts.js +9 -15
- package/dist/artifacts.js.map +1 -1
- package/dist/host-process.d.ts +2 -2
- package/dist/host-process.d.ts.map +1 -1
- package/dist/host-process.js +206 -5
- package/dist/host-process.js.map +1 -1
- package/dist/index.d.ts +150 -11
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +166 -47
- package/dist/index.js.map +1 -1
- package/dist/launch-options.d.ts +9 -2
- package/dist/launch-options.d.ts.map +1 -1
- package/dist/spawn-options.d.ts +4 -1
- package/dist/spawn-options.d.ts.map +1 -1
- package/package.json +3 -3
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
|
+
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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,
|
|
124
|
-
|
|
125
|
-
image
|
|
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.
|
|
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.
|
|
173
|
-
conn.
|
|
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.
|
|
179
|
-
|
|
180
|
-
|
|
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 (
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
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
|
|
197
|
-
|
|
198
|
-
|
|
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
|
-
|
|
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
|
package/dist/artifacts.d.ts
CHANGED
|
@@ -1,16 +1,15 @@
|
|
|
1
1
|
type SandboxTarget = {
|
|
2
2
|
readonly packageName: string;
|
|
3
3
|
readonly hostBinaryName: string;
|
|
4
|
-
readonly
|
|
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.
|
|
13
|
-
export declare function builtInRootfsIdentity(name: "alpine:3.
|
|
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;
|
package/dist/artifacts.d.ts.map
CHANGED
|
@@ -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,
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
40
|
-
if (name === "alpine:3.
|
|
33
|
+
export function builtInRootfsPath(name) {
|
|
34
|
+
if (name === "alpine:3.23") {
|
|
41
35
|
const target = currentSandboxTarget();
|
|
42
|
-
return resolveArtifactPath(target, target.
|
|
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
|
|
47
|
-
if (name === "alpine:3.
|
|
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
|
-
|
|
47
|
+
"qcow2",
|
|
54
48
|
target.platform,
|
|
55
49
|
target.arch,
|
|
56
50
|
target.libc ?? "none",
|
|
57
51
|
target.packageName,
|
|
58
52
|
packageVersion,
|
|
59
|
-
target.
|
|
53
|
+
target.rootfsName,
|
|
60
54
|
].join(":");
|
|
61
55
|
}
|
|
62
56
|
throw new Error(`unsupported built-in rootfs: ${name}`);
|
package/dist/artifacts.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"artifacts.js","sourceRoot":"","sources":["../src/artifacts.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAC5C,OAAO,EAAE,SAAS,EAAE,MAAM,oBAAoB,CAAC;AAE/C,MAAM,OAAO,GAAG,aAAa,CAAC,OAAO,IAAI,CAAC,GAAG,CAAC,CAAC;
|
|
1
|
+
{"version":3,"file":"artifacts.js","sourceRoot":"","sources":["../src/artifacts.ts"],"names":[],"mappings":"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"}
|
package/dist/host-process.d.ts
CHANGED
|
@@ -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
|
|
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,
|
|
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"}
|
package/dist/host-process.js
CHANGED
|
@@ -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
|
-
|
|
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) {
|