@torkbot/sandbox 0.1.0-pre.0 → 0.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,705 @@
1
+ import { spawn } from "node:child_process";
2
+ import { once } from "node:events";
3
+ import { Binary, BSON } from "bson";
4
+ import { hostBinaryPath, macosHostSigningError } from "./artifacts.js";
5
+ import { isSandboxWritableFileSystem } from "./vfs.js";
6
+ export class HostProcessSandboxVm {
7
+ hasControlSocket = true;
8
+ packets;
9
+ #child;
10
+ #options;
11
+ #packets = new AsyncQueue();
12
+ #packetActivity = new AsyncSignal();
13
+ #hostFs = new Map();
14
+ #requestHeaderHooks;
15
+ #buffer = new Uint8Array();
16
+ #stderr = "";
17
+ #closed = false;
18
+ #exitError = null;
19
+ #stdinError = null;
20
+ constructor(child, options, requestHeaderHooks) {
21
+ this.#child = child;
22
+ this.#options = options;
23
+ this.packets = this.#packets;
24
+ this.#requestHeaderHooks = requestHeaderHooks;
25
+ for (const mount of options.mounts ?? []) {
26
+ if (mount.kind === "virtual-fs") {
27
+ this.#hostFs.set(mount.path, mount.fileSystem);
28
+ }
29
+ }
30
+ child.stdout.on("data", (chunk) => {
31
+ this.#receive(chunk);
32
+ });
33
+ child.stderr.on("data", (chunk) => {
34
+ const text = chunk.toString("utf8").trim();
35
+ if (text.length > 0) {
36
+ this.#stderr = this.#stderr.length === 0 ? text : `${this.#stderr}\n${text}`;
37
+ }
38
+ });
39
+ child.stdin.on("error", (error) => {
40
+ this.#stdinError = error;
41
+ if (this.#exitError === null) {
42
+ this.#exitError = new Error(`sandbox-host stdin failed: ${error.message}`);
43
+ }
44
+ });
45
+ child.on("exit", (code, signal) => {
46
+ if (this.#closed) {
47
+ return;
48
+ }
49
+ const exitText = signal === null
50
+ ? `sandbox-host exited with ${code ?? "unknown status"}`
51
+ : `sandbox-host exited from signal ${signal}`;
52
+ this.#exitError = new Error(this.#stderr.length === 0 ? exitText : `${exitText}\n${this.#stderr}`);
53
+ this.#packets.close(this.#exitError);
54
+ this.#packetActivity.close(this.#exitError);
55
+ });
56
+ }
57
+ static async spawn(options, hostOptions, requestHeaderHooks = new Map()) {
58
+ let vm;
59
+ const hostPath = hostBinaryPath();
60
+ try {
61
+ const child = spawn(hostPath, ["--stdio"], {
62
+ stdio: ["pipe", "pipe", "pipe"],
63
+ });
64
+ vm = new HostProcessSandboxVm(child, options, requestHeaderHooks);
65
+ await Promise.race([
66
+ once(child, "spawn"),
67
+ once(child, "error").then(([error]) => {
68
+ throw error;
69
+ }),
70
+ ]);
71
+ vm.#writeToHost(encodeHostSpawn(hostOptions));
72
+ await vm.#waitForLaunch();
73
+ return vm;
74
+ }
75
+ catch (error) {
76
+ await vm?.close();
77
+ const signingError = macosHostSigningError(hostPath);
78
+ if (signingError !== null) {
79
+ throw signingError;
80
+ }
81
+ throw error;
82
+ }
83
+ }
84
+ writeControlPacket(packet) {
85
+ this.#assertOpen();
86
+ this.#writeToHost(packet);
87
+ }
88
+ async close() {
89
+ if (this.#closed) {
90
+ return;
91
+ }
92
+ this.#closed = true;
93
+ this.#packets.close();
94
+ this.#packetActivity.close();
95
+ const exited = this.#child.exitCode !== null || this.#child.signalCode !== null
96
+ ? Promise.resolve()
97
+ : once(this.#child, "exit").then(() => undefined);
98
+ this.#child.stdin.destroy();
99
+ this.#child.kill("SIGTERM");
100
+ await Promise.race([
101
+ exited,
102
+ delay(500),
103
+ ]);
104
+ if (this.#child.exitCode !== null || this.#child.signalCode !== null) {
105
+ return;
106
+ }
107
+ this.#child.kill("SIGKILL");
108
+ await Promise.race([
109
+ exited,
110
+ delay(1_000),
111
+ ]);
112
+ }
113
+ async terminateHostForTest() {
114
+ if (this.#child.exitCode !== null || this.#child.signalCode !== null) {
115
+ return;
116
+ }
117
+ const exited = once(this.#child, "exit").then(() => undefined);
118
+ this.#child.kill("SIGTERM");
119
+ await Promise.race([
120
+ exited,
121
+ delay(1_000),
122
+ ]);
123
+ if (this.#child.exitCode !== null || this.#child.signalCode !== null) {
124
+ return;
125
+ }
126
+ this.#child.kill("SIGKILL");
127
+ await exited;
128
+ }
129
+ #assertOpen() {
130
+ if (this.#closed) {
131
+ throw new Error("sandbox VM is closed");
132
+ }
133
+ if (this.#stdinError !== null) {
134
+ throw this.#stdinError;
135
+ }
136
+ if (this.#exitError !== null) {
137
+ throw this.#exitError;
138
+ }
139
+ }
140
+ #writeToHost(packet) {
141
+ this.#assertOpen();
142
+ this.#writeOpenPacket(packet);
143
+ }
144
+ #writeOpenPacket(packet) {
145
+ if (!this.#child.stdin.writable) {
146
+ throw new Error("sandbox-host stdin is closed");
147
+ }
148
+ this.#child.stdin.write(packet, (error) => {
149
+ if (error !== null && error !== undefined) {
150
+ this.#stdinError = error;
151
+ if (this.#exitError === null) {
152
+ this.#exitError = new Error(`sandbox-host stdin failed: ${error.message}`);
153
+ }
154
+ }
155
+ });
156
+ }
157
+ #tryWriteToHost(packet) {
158
+ if (this.#closed || this.#stdinError !== null || this.#exitError !== null) {
159
+ return;
160
+ }
161
+ try {
162
+ this.#writeOpenPacket(packet);
163
+ }
164
+ catch (error) {
165
+ if (!this.#closed && this.#stdinError === null && this.#exitError === null) {
166
+ throw error;
167
+ }
168
+ }
169
+ }
170
+ #receive(chunk) {
171
+ const next = new Uint8Array(this.#buffer.byteLength + chunk.byteLength);
172
+ next.set(this.#buffer);
173
+ next.set(chunk, this.#buffer.byteLength);
174
+ this.#buffer = next;
175
+ while (this.#buffer.byteLength >= 4) {
176
+ const frameLength = new DataView(this.#buffer.buffer, this.#buffer.byteOffset, 4).getUint32(0, true);
177
+ const packetLength = 4 + frameLength;
178
+ if (this.#buffer.byteLength < packetLength) {
179
+ return;
180
+ }
181
+ const packet = this.#buffer.slice(0, packetLength);
182
+ this.#buffer = this.#buffer.slice(packetLength);
183
+ this.#packetActivity.notify();
184
+ if (!this.#routeHostPacket(packet)) {
185
+ this.#packets.push(packet);
186
+ }
187
+ }
188
+ }
189
+ #routeHostPacket(packet) {
190
+ let document;
191
+ try {
192
+ document = BSON.deserialize(packet.slice(4));
193
+ }
194
+ catch {
195
+ return false;
196
+ }
197
+ const type = document.type;
198
+ if (type !== "host.vfs.stat"
199
+ && type !== "host.vfs.list"
200
+ && type !== "host.vfs.read"
201
+ && type !== "host.vfs.create"
202
+ && type !== "host.vfs.write"
203
+ && type !== "host.vfs.truncate"
204
+ && type !== "host.vfs.mkdir"
205
+ && type !== "host.vfs.unlink"
206
+ && type !== "host.vfs.rmdir"
207
+ && type !== "host.vfs.rename"
208
+ && type !== "host.vfs.symlink"
209
+ && type !== "host.vfs.readlink"
210
+ && type !== "host.http.requestHeaders"
211
+ && type !== "host.http.activeRequestHeaderHooks") {
212
+ return false;
213
+ }
214
+ if (type === "host.http.requestHeaders") {
215
+ void this.#handleHttpRequestHeaders(document);
216
+ }
217
+ else if (type === "host.http.activeRequestHeaderHooks") {
218
+ void this.#handleActiveRequestHeaderHooks(document);
219
+ }
220
+ else {
221
+ void this.#handleVirtualFsRequest(document);
222
+ }
223
+ return true;
224
+ }
225
+ async #handleHttpRequestHeaders(document) {
226
+ const id = typeof document.id === "string" ? document.id : "";
227
+ try {
228
+ const hookIds = assertStringArray(document.hookIds, "hookIds");
229
+ const originalHeaders = assertHeaderPairs(document.headers, "headers");
230
+ const headers = new Headers(originalHeaders);
231
+ const mutatedHeaders = trackHeaderMutations(headers);
232
+ const request = {
233
+ protocol: assertProtocol(document.protocol),
234
+ url: new URL(assertString(document.url, "url")),
235
+ method: assertString(document.method, "method"),
236
+ headers,
237
+ destination: {
238
+ originalIp: assertString(document.originalDestinationIp, "originalDestinationIp"),
239
+ originalPort: assertNumber(document.originalDestinationPort, "originalDestinationPort"),
240
+ upstreamIp: assertString(document.upstreamDialIp, "upstreamDialIp"),
241
+ upstreamPort: assertNumber(document.upstreamDialPort, "upstreamDialPort"),
242
+ },
243
+ tls: optionalTlsMetadata(document.tls),
244
+ };
245
+ for (const hookId of hookIds) {
246
+ const registration = this.#requestHeaderHooks.get(hookId);
247
+ if (registration?.active === true) {
248
+ await registration.hook(request);
249
+ }
250
+ }
251
+ this.#tryWriteToHost(encodePacket({
252
+ type: "host.http.response",
253
+ id,
254
+ ok: true,
255
+ headers: mutatedHeaders()
256
+ ? Array.from(headers.entries())
257
+ : originalHeaders,
258
+ }));
259
+ }
260
+ catch (error) {
261
+ this.#tryWriteToHost(encodePacket({
262
+ type: "host.http.response",
263
+ id,
264
+ ok: false,
265
+ error: error instanceof Error ? error.message : String(error),
266
+ }));
267
+ }
268
+ }
269
+ async #handleActiveRequestHeaderHooks(document) {
270
+ const id = typeof document.id === "string" ? document.id : "";
271
+ try {
272
+ const hookIds = assertStringArray(document.hookIds, "hookIds");
273
+ this.#tryWriteToHost(encodePacket({
274
+ type: "host.http.response",
275
+ id,
276
+ ok: true,
277
+ hookIds: hookIds.filter((hookId) => this.#requestHeaderHooks.get(hookId)?.active === true),
278
+ }));
279
+ }
280
+ catch (error) {
281
+ this.#tryWriteToHost(encodePacket({
282
+ type: "host.http.response",
283
+ id,
284
+ ok: false,
285
+ error: error instanceof Error ? error.message : String(error),
286
+ }));
287
+ }
288
+ }
289
+ async #handleVirtualFsRequest(document) {
290
+ const id = typeof document.id === "string" ? document.id : "";
291
+ try {
292
+ const mountPath = assertString(document.mountPath, "mountPath");
293
+ const fileSystem = this.#hostFs.get(mountPath);
294
+ if (fileSystem === undefined) {
295
+ throw new Error(`host filesystem mount not found: ${mountPath}`);
296
+ }
297
+ switch (document.type) {
298
+ case "host.vfs.stat": {
299
+ const path = assertString(document.path, "path");
300
+ this.#tryWriteToHost(encodePacket({
301
+ type: "host.vfs.response",
302
+ id,
303
+ ok: true,
304
+ stat: await fileSystem.stat(path),
305
+ }));
306
+ return;
307
+ }
308
+ case "host.vfs.list": {
309
+ const path = assertString(document.path, "path");
310
+ this.#tryWriteToHost(encodePacket({
311
+ type: "host.vfs.response",
312
+ id,
313
+ ok: true,
314
+ entries: await fileSystem.list(path),
315
+ }));
316
+ return;
317
+ }
318
+ case "host.vfs.read": {
319
+ const path = assertString(document.path, "path");
320
+ const offset = assertNumber(document.offset, "offset");
321
+ const size = assertNumber(document.size, "size");
322
+ const contents = await fileSystem.read({
323
+ path,
324
+ range: { offset, length: size },
325
+ signal: AbortSignal.timeout(30_000),
326
+ });
327
+ this.#tryWriteToHost(encodePacket({
328
+ type: "host.vfs.response",
329
+ id,
330
+ ok: true,
331
+ contents,
332
+ }));
333
+ return;
334
+ }
335
+ case "host.vfs.create": {
336
+ const path = assertString(document.path, "path");
337
+ if (!isSandboxWritableFileSystem(fileSystem)) {
338
+ throw new Error(`host filesystem mount is read-only: ${mountPath}`);
339
+ }
340
+ this.#tryWriteToHost(encodePacket({
341
+ type: "host.vfs.response",
342
+ id,
343
+ ok: true,
344
+ stat: await fileSystem.createFile(path),
345
+ }));
346
+ return;
347
+ }
348
+ case "host.vfs.write": {
349
+ const path = assertString(document.path, "path");
350
+ if (!isSandboxWritableFileSystem(fileSystem)) {
351
+ throw new Error(`host filesystem mount is read-only: ${mountPath}`);
352
+ }
353
+ const offset = assertNumber(document.offset, "offset");
354
+ this.#tryWriteToHost(encodePacket({
355
+ type: "host.vfs.response",
356
+ id,
357
+ ok: true,
358
+ written: await fileSystem.write({
359
+ path,
360
+ offset,
361
+ contents: binaryField(document.contents, "contents"),
362
+ }),
363
+ }));
364
+ return;
365
+ }
366
+ case "host.vfs.truncate": {
367
+ const path = assertString(document.path, "path");
368
+ if (!isSandboxWritableFileSystem(fileSystem)) {
369
+ throw new Error(`host filesystem mount is read-only: ${mountPath}`);
370
+ }
371
+ const size = assertNumber(document.size, "size");
372
+ this.#tryWriteToHost(encodePacket({
373
+ type: "host.vfs.response",
374
+ id,
375
+ ok: true,
376
+ stat: await fileSystem.truncate(path, size),
377
+ }));
378
+ return;
379
+ }
380
+ case "host.vfs.mkdir": {
381
+ const path = assertString(document.path, "path");
382
+ const posix = assertPosixFileSystem(fileSystem, mountPath);
383
+ this.#tryWriteToHost(encodePacket({
384
+ type: "host.vfs.response",
385
+ id,
386
+ ok: true,
387
+ stat: await posix.mkdir(path),
388
+ }));
389
+ return;
390
+ }
391
+ case "host.vfs.unlink": {
392
+ const path = assertString(document.path, "path");
393
+ const posix = assertPosixFileSystem(fileSystem, mountPath);
394
+ await posix.unlink(path);
395
+ this.#tryWriteToHost(encodePacket({
396
+ type: "host.vfs.response",
397
+ id,
398
+ ok: true,
399
+ }));
400
+ return;
401
+ }
402
+ case "host.vfs.rmdir": {
403
+ const path = assertString(document.path, "path");
404
+ const posix = assertPosixFileSystem(fileSystem, mountPath);
405
+ await posix.rmdir(path);
406
+ this.#tryWriteToHost(encodePacket({
407
+ type: "host.vfs.response",
408
+ id,
409
+ ok: true,
410
+ }));
411
+ return;
412
+ }
413
+ case "host.vfs.rename": {
414
+ const posix = assertPosixFileSystem(fileSystem, mountPath);
415
+ const from = assertString(document.from, "from");
416
+ const to = assertString(document.to, "to");
417
+ const flags = assertNumber(document.flags, "flags");
418
+ await posix.rename(from, to, flags);
419
+ this.#tryWriteToHost(encodePacket({
420
+ type: "host.vfs.response",
421
+ id,
422
+ ok: true,
423
+ }));
424
+ return;
425
+ }
426
+ case "host.vfs.symlink": {
427
+ const path = assertString(document.path, "path");
428
+ const posix = assertPosixFileSystem(fileSystem, mountPath);
429
+ const target = assertString(document.target, "target");
430
+ const stat = await posix.symlink(target, path);
431
+ this.#tryWriteToHost(encodePacket({
432
+ type: "host.vfs.response",
433
+ id,
434
+ ok: true,
435
+ stat,
436
+ }));
437
+ return;
438
+ }
439
+ case "host.vfs.readlink": {
440
+ const path = assertString(document.path, "path");
441
+ const posix = assertPosixFileSystem(fileSystem, mountPath);
442
+ this.#tryWriteToHost(encodePacket({
443
+ type: "host.vfs.response",
444
+ id,
445
+ ok: true,
446
+ target: await posix.readlink(path),
447
+ }));
448
+ return;
449
+ }
450
+ }
451
+ }
452
+ catch (error) {
453
+ this.#tryWriteToHost(encodePacket({
454
+ type: "host.vfs.response",
455
+ id,
456
+ ok: false,
457
+ error: error instanceof Error ? error.message : String(error),
458
+ }));
459
+ }
460
+ }
461
+ async #waitForLaunch() {
462
+ await Promise.race([
463
+ this.#packetActivity.wait(),
464
+ once(this.#child, "exit").then(() => {
465
+ throw this.#exitError ?? new Error("sandbox-host exited before VM launch completed");
466
+ }),
467
+ delay(10_000).then(() => {
468
+ throw new Error("sandbox-host did not produce a launch acknowledgement");
469
+ }),
470
+ ]);
471
+ }
472
+ }
473
+ export { hostBinaryPath };
474
+ function assertString(value, field) {
475
+ if (typeof value !== "string") {
476
+ throw new Error(`host vfs request ${field} must be a string`);
477
+ }
478
+ return value;
479
+ }
480
+ function assertNumber(value, field) {
481
+ if (typeof value !== "number" || !Number.isSafeInteger(value) || value < 0) {
482
+ throw new Error(`host request ${field} must be a non-negative safe integer`);
483
+ }
484
+ return value;
485
+ }
486
+ function assertStringArray(value, field) {
487
+ if (!Array.isArray(value) || value.some((entry) => typeof entry !== "string")) {
488
+ throw new Error(`host request ${field} must be a string array`);
489
+ }
490
+ return value;
491
+ }
492
+ function assertHeaderPairs(value, field) {
493
+ if (!Array.isArray(value)
494
+ || value.some((entry) => {
495
+ return !Array.isArray(entry)
496
+ || entry.length !== 2
497
+ || typeof entry[0] !== "string"
498
+ || typeof entry[1] !== "string";
499
+ })) {
500
+ throw new Error(`host request ${field} must be header pairs`);
501
+ }
502
+ return value;
503
+ }
504
+ function trackHeaderMutations(headers) {
505
+ let mutated = false;
506
+ const set = headers.set.bind(headers);
507
+ const append = headers.append.bind(headers);
508
+ const deleteHeader = headers.delete.bind(headers);
509
+ Object.defineProperties(headers, {
510
+ set: {
511
+ value(name, value) {
512
+ mutated = true;
513
+ set(name, value);
514
+ },
515
+ },
516
+ append: {
517
+ value(name, value) {
518
+ mutated = true;
519
+ append(name, value);
520
+ },
521
+ },
522
+ delete: {
523
+ value(name) {
524
+ mutated = true;
525
+ deleteHeader(name);
526
+ },
527
+ },
528
+ });
529
+ return () => mutated;
530
+ }
531
+ function assertProtocol(value) {
532
+ if (value === "http/1.1" || value === "h2") {
533
+ return value;
534
+ }
535
+ throw new Error("host request protocol must be http/1.1 or h2");
536
+ }
537
+ function optionalTlsMetadata(value) {
538
+ if (value === undefined || value === null) {
539
+ return undefined;
540
+ }
541
+ if (typeof value !== "object") {
542
+ throw new Error("host request tls must be a document");
543
+ }
544
+ const document = value;
545
+ return {
546
+ sni: document.sni === undefined || document.sni === null
547
+ ? undefined
548
+ : assertString(document.sni, "tls.sni"),
549
+ alpn: document.alpn === undefined || document.alpn === null
550
+ ? undefined
551
+ : assertString(document.alpn, "tls.alpn"),
552
+ };
553
+ }
554
+ function binaryField(value, field) {
555
+ if (value instanceof Uint8Array) {
556
+ return value;
557
+ }
558
+ if (value instanceof Binary) {
559
+ return value.buffer;
560
+ }
561
+ throw new Error(`host request ${field} must be binary`);
562
+ }
563
+ function assertPosixFileSystem(fileSystem, mountPath) {
564
+ const candidate = fileSystem;
565
+ if (!isSandboxWritableFileSystem(fileSystem)
566
+ || typeof candidate.mkdir !== "function"
567
+ || typeof candidate.unlink !== "function"
568
+ || typeof candidate.rmdir !== "function"
569
+ || typeof candidate.rename !== "function"
570
+ || typeof candidate.symlink !== "function"
571
+ || typeof candidate.readlink !== "function") {
572
+ throw new Error(`host filesystem mount does not support POSIX mutations: ${mountPath}`);
573
+ }
574
+ return candidate;
575
+ }
576
+ function delay(milliseconds) {
577
+ return new Promise((resolvePromise) => setTimeout(resolvePromise, milliseconds));
578
+ }
579
+ class AsyncQueue {
580
+ #values = [];
581
+ #nextWaiters = [];
582
+ #closed = false;
583
+ #error;
584
+ get length() {
585
+ return this.#values.length;
586
+ }
587
+ push(value) {
588
+ if (this.#closed) {
589
+ return;
590
+ }
591
+ const nextWaiter = this.#nextWaiters.shift();
592
+ if (nextWaiter !== undefined) {
593
+ nextWaiter.resolve({ value, done: false });
594
+ }
595
+ else {
596
+ this.#values.push(value);
597
+ }
598
+ }
599
+ close(error) {
600
+ if (this.#closed) {
601
+ return;
602
+ }
603
+ this.#closed = true;
604
+ this.#error = error;
605
+ for (const waiter of this.#nextWaiters.splice(0)) {
606
+ if (error === undefined) {
607
+ waiter.resolve({ value: undefined, done: true });
608
+ }
609
+ else {
610
+ waiter.reject(error);
611
+ }
612
+ }
613
+ }
614
+ [Symbol.asyncIterator]() {
615
+ return {
616
+ next: async () => {
617
+ const value = this.#values.shift();
618
+ if (value !== undefined) {
619
+ return { value, done: false };
620
+ }
621
+ if (this.#closed) {
622
+ if (this.#error !== undefined) {
623
+ throw this.#error;
624
+ }
625
+ return { value: undefined, done: true };
626
+ }
627
+ return await new Promise((resolve, reject) => {
628
+ this.#nextWaiters.push({
629
+ resolve,
630
+ reject,
631
+ });
632
+ });
633
+ },
634
+ };
635
+ }
636
+ }
637
+ class AsyncSignal {
638
+ #waiters = [];
639
+ #signaled = false;
640
+ #closed = false;
641
+ #error;
642
+ notify() {
643
+ if (this.#closed) {
644
+ return;
645
+ }
646
+ this.#signaled = true;
647
+ for (const waiter of this.#waiters.splice(0)) {
648
+ waiter.resolve();
649
+ }
650
+ }
651
+ close(error) {
652
+ if (this.#closed) {
653
+ return;
654
+ }
655
+ this.#closed = true;
656
+ this.#error = error;
657
+ for (const waiter of this.#waiters.splice(0)) {
658
+ if (error === undefined) {
659
+ waiter.resolve();
660
+ }
661
+ else {
662
+ waiter.reject(error);
663
+ }
664
+ }
665
+ }
666
+ async wait() {
667
+ if (this.#signaled) {
668
+ return;
669
+ }
670
+ if (this.#closed) {
671
+ if (this.#error !== undefined) {
672
+ throw this.#error;
673
+ }
674
+ throw new Error("sandbox-host packet activity closed");
675
+ }
676
+ return await new Promise((resolve, reject) => {
677
+ this.#waiters.push({ resolve, reject });
678
+ });
679
+ }
680
+ }
681
+ function encodeHostSpawn(options) {
682
+ return encodePacket({
683
+ type: "host.spawn",
684
+ name: options.name,
685
+ vcpus: options.cpu?.vcpus,
686
+ memoryMib: options.memory?.mib,
687
+ kernelFormat: options.kernel.format,
688
+ initCrate: options.init.crateName,
689
+ rootfsPath: options.rootfs.path,
690
+ rootfsReadonly: options.rootfs.readonly,
691
+ rootfsFormat: options.rootfs.format,
692
+ rootfsOverlayMode: options.rootfsOverlay?.mode,
693
+ mounts: options.mounts ?? [],
694
+ networkOutbound: options.network?.outbound,
695
+ networkHttp: options.network?.http === undefined ? undefined : options.network.http,
696
+ });
697
+ }
698
+ function encodePacket(document) {
699
+ const frame = BSON.serialize(document, { ignoreUndefined: true });
700
+ const packet = new Uint8Array(4 + frame.byteLength);
701
+ new DataView(packet.buffer, packet.byteOffset, 4).setUint32(0, frame.byteLength, true);
702
+ packet.set(frame, 4);
703
+ return packet;
704
+ }
705
+ //# sourceMappingURL=host-process.js.map