@secure-exec/browser 0.2.0-rc.1 → 0.3.0-rc.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.
@@ -1,5 +1,7 @@
1
- import { createNetworkStub } from "@secure-exec/core";
2
- import { getBrowserSystemDriverOptions, } from "./driver.js";
1
+ import { getBrowserSystemDriverOptions } from "./driver.js";
2
+ import { createFsStub, createNetworkStub, loadFile, mkdir, resolveModule, } from "./runtime.js";
3
+ import { assertBrowserSyncBridgeSupport, createBrowserSyncBridgePayload, SYNC_BRIDGE_KIND_BINARY, SYNC_BRIDGE_KIND_JSON, SYNC_BRIDGE_KIND_NONE, SYNC_BRIDGE_KIND_TEXT, SYNC_BRIDGE_PAYLOAD_LIMIT_ERROR_CODE, SYNC_BRIDGE_SIGNAL_KIND_INDEX, SYNC_BRIDGE_SIGNAL_LENGTH_INDEX, SYNC_BRIDGE_SIGNAL_STATE_INDEX, SYNC_BRIDGE_SIGNAL_STATE_READY, SYNC_BRIDGE_SIGNAL_STATUS_INDEX, SYNC_BRIDGE_STATUS_ERROR, SYNC_BRIDGE_STATUS_OK, toBrowserSyncBridgeError, } from "./sync-bridge.js";
4
+ const DEFAULT_BROWSER_TIMING_MITIGATION = "freeze";
3
5
  const BROWSER_OPTION_VALIDATORS = [
4
6
  {
5
7
  label: "memoryLimit",
@@ -9,10 +11,6 @@ const BROWSER_OPTION_VALIDATORS = [
9
11
  label: "cpuTimeLimitMs",
10
12
  hasValue: (options) => options.cpuTimeLimitMs !== undefined,
11
13
  },
12
- {
13
- label: "timingMitigation",
14
- hasValue: (options) => options.timingMitigation !== undefined,
15
- },
16
14
  ];
17
15
  function serializePermissions(permissions) {
18
16
  if (!permissions) {
@@ -35,6 +33,12 @@ function resolveWorkerUrl(workerUrl) {
35
33
  }
36
34
  return new URL("./worker.js", import.meta.url);
37
35
  }
36
+ function createWorkerControlToken() {
37
+ if (typeof globalThis.crypto?.randomUUID === "function") {
38
+ return globalThis.crypto.randomUUID();
39
+ }
40
+ return `browser-runtime-${Date.now()}-${Math.random().toString(16).slice(2)}`;
41
+ }
38
42
  function toBrowserWorkerExecOptions(options) {
39
43
  if (!options) {
40
44
  return undefined;
@@ -44,12 +48,11 @@ function toBrowserWorkerExecOptions(options) {
44
48
  env: options.env,
45
49
  cwd: options.cwd,
46
50
  stdin: options.stdin,
51
+ timingMitigation: options.timingMitigation,
47
52
  };
48
53
  }
49
54
  function validateBrowserRuntimeOptions(options) {
50
- const unsupported = BROWSER_OPTION_VALIDATORS
51
- .filter((validator) => validator.hasValue(options))
52
- .map((validator) => validator.label);
55
+ const unsupported = BROWSER_OPTION_VALIDATORS.filter((validator) => validator.hasValue(options)).map((validator) => validator.label);
53
56
  if (unsupported.length === 0) {
54
57
  return;
55
58
  }
@@ -60,30 +63,171 @@ function validateBrowserExecOptions(options) {
60
63
  if (options?.cpuTimeLimitMs !== undefined) {
61
64
  unsupported.push("cpuTimeLimitMs");
62
65
  }
63
- if (options?.timingMitigation !== undefined) {
64
- unsupported.push("timingMitigation");
65
- }
66
66
  if (unsupported.length === 0) {
67
67
  return;
68
68
  }
69
69
  throw new Error(`Browser runtime does not support Node-only exec options: ${unsupported.join(", ")}`);
70
70
  }
71
+ function isStdioMessage(message) {
72
+ return message.type === "stdio";
73
+ }
74
+ function isResponseMessage(message) {
75
+ return message.type === "response";
76
+ }
77
+ function isSyncRequestMessage(message) {
78
+ return message.type === "sync-request";
79
+ }
80
+ function createSyncBridgeFilesystem(options) {
81
+ return options.system.filesystem ?? createFsStub();
82
+ }
83
+ function throwBridgePayloadTooLarge(label, actualBytes, maxBytes) {
84
+ const error = new Error(`[${SYNC_BRIDGE_PAYLOAD_LIMIT_ERROR_CODE}] ${label}: payload is ${actualBytes} bytes, limit is ${maxBytes} bytes`);
85
+ error.code = SYNC_BRIDGE_PAYLOAD_LIMIT_ERROR_CODE;
86
+ throw error;
87
+ }
88
+ function toUint8Array(value) {
89
+ if (value instanceof Uint8Array) {
90
+ return value;
91
+ }
92
+ if (ArrayBuffer.isView(value)) {
93
+ return new Uint8Array(value.buffer, value.byteOffset, value.byteLength);
94
+ }
95
+ if (value instanceof ArrayBuffer) {
96
+ return new Uint8Array(value);
97
+ }
98
+ return new TextEncoder().encode(String(value));
99
+ }
100
+ function createSyncBridgeResponseBytes(response, encoder) {
101
+ switch (response.kind) {
102
+ case SYNC_BRIDGE_KIND_NONE:
103
+ return new Uint8Array(0);
104
+ case SYNC_BRIDGE_KIND_TEXT:
105
+ return encoder.encode(response.value);
106
+ case SYNC_BRIDGE_KIND_BINARY:
107
+ return response.value;
108
+ case SYNC_BRIDGE_KIND_JSON:
109
+ return encoder.encode(JSON.stringify(response.value));
110
+ default:
111
+ return new Uint8Array(0);
112
+ }
113
+ }
114
+ async function handleSyncBridgeOperation(filesystem, message) {
115
+ switch (message.operation) {
116
+ case "fs.readFile":
117
+ return {
118
+ kind: SYNC_BRIDGE_KIND_TEXT,
119
+ value: await filesystem.readTextFile(String(message.args[0])),
120
+ };
121
+ case "fs.writeFile":
122
+ await filesystem.writeFile(String(message.args[0]), String(message.args[1] ?? ""));
123
+ return { kind: SYNC_BRIDGE_KIND_NONE };
124
+ case "fs.readFileBinary":
125
+ return {
126
+ kind: SYNC_BRIDGE_KIND_BINARY,
127
+ value: await filesystem.readFile(String(message.args[0])),
128
+ };
129
+ case "fs.writeFileBinary":
130
+ await filesystem.writeFile(String(message.args[0]), toUint8Array(message.args[1]));
131
+ return { kind: SYNC_BRIDGE_KIND_NONE };
132
+ case "fs.readDir":
133
+ return {
134
+ kind: SYNC_BRIDGE_KIND_JSON,
135
+ value: await filesystem.readDirWithTypes(String(message.args[0])),
136
+ };
137
+ case "fs.createDir":
138
+ await filesystem.createDir(String(message.args[0]));
139
+ return { kind: SYNC_BRIDGE_KIND_NONE };
140
+ case "fs.mkdir":
141
+ await mkdir(filesystem, String(message.args[0]));
142
+ return { kind: SYNC_BRIDGE_KIND_NONE };
143
+ case "fs.rmdir":
144
+ await filesystem.removeDir(String(message.args[0]));
145
+ return { kind: SYNC_BRIDGE_KIND_NONE };
146
+ case "fs.exists":
147
+ return {
148
+ kind: SYNC_BRIDGE_KIND_JSON,
149
+ value: await filesystem.exists(String(message.args[0])),
150
+ };
151
+ case "fs.stat":
152
+ return {
153
+ kind: SYNC_BRIDGE_KIND_JSON,
154
+ value: await filesystem.stat(String(message.args[0])),
155
+ };
156
+ case "fs.lstat":
157
+ return {
158
+ kind: SYNC_BRIDGE_KIND_JSON,
159
+ value: await filesystem.lstat(String(message.args[0])),
160
+ };
161
+ case "fs.unlink":
162
+ await filesystem.removeFile(String(message.args[0]));
163
+ return { kind: SYNC_BRIDGE_KIND_NONE };
164
+ case "fs.rename":
165
+ await filesystem.rename(String(message.args[0]), String(message.args[1]));
166
+ return { kind: SYNC_BRIDGE_KIND_NONE };
167
+ case "fs.realpath":
168
+ return {
169
+ kind: SYNC_BRIDGE_KIND_TEXT,
170
+ value: await filesystem.realpath(String(message.args[0])),
171
+ };
172
+ case "fs.readlink":
173
+ return {
174
+ kind: SYNC_BRIDGE_KIND_TEXT,
175
+ value: await filesystem.readlink(String(message.args[0])),
176
+ };
177
+ case "fs.symlink":
178
+ await filesystem.symlink(String(message.args[0]), String(message.args[1]));
179
+ return { kind: SYNC_BRIDGE_KIND_NONE };
180
+ case "fs.link":
181
+ await filesystem.link(String(message.args[0]), String(message.args[1]));
182
+ return { kind: SYNC_BRIDGE_KIND_NONE };
183
+ case "fs.chmod":
184
+ await filesystem.chmod(String(message.args[0]), Number(message.args[1]));
185
+ return { kind: SYNC_BRIDGE_KIND_NONE };
186
+ case "fs.truncate":
187
+ await filesystem.truncate(String(message.args[0]), Number(message.args[1]));
188
+ return { kind: SYNC_BRIDGE_KIND_NONE };
189
+ case "module.resolve": {
190
+ const resolved = await resolveModule(String(message.args[0]), String(message.args[1]), filesystem);
191
+ return resolved === null
192
+ ? { kind: SYNC_BRIDGE_KIND_NONE }
193
+ : { kind: SYNC_BRIDGE_KIND_TEXT, value: resolved };
194
+ }
195
+ case "module.loadFile": {
196
+ const source = await loadFile(String(message.args[0]), filesystem);
197
+ return source === null
198
+ ? { kind: SYNC_BRIDGE_KIND_NONE }
199
+ : { kind: SYNC_BRIDGE_KIND_TEXT, value: source };
200
+ }
201
+ default:
202
+ throw new Error(`Unsupported browser sync bridge operation: ${String(message.operation)}`);
203
+ }
204
+ }
71
205
  export class BrowserRuntimeDriver {
72
- options;
73
206
  worker;
74
207
  pending = new Map();
208
+ controlToken = createWorkerControlToken();
75
209
  defaultOnStdio;
210
+ defaultTimingMitigation;
76
211
  networkAdapter;
212
+ syncBridge;
213
+ syncFilesystem;
77
214
  ready;
215
+ encoder = new TextEncoder();
78
216
  nextId = 1;
79
217
  disposed = false;
80
218
  constructor(options, factoryOptions = {}) {
81
- this.options = options;
82
219
  if (typeof Worker === "undefined") {
83
220
  throw new Error("Browser runtime requires a global Worker implementation");
84
221
  }
222
+ assertBrowserSyncBridgeSupport();
85
223
  this.defaultOnStdio = options.onStdio;
224
+ this.defaultTimingMitigation =
225
+ options.timingMitigation ??
226
+ options.runtime.process.timingMitigation ??
227
+ DEFAULT_BROWSER_TIMING_MITIGATION;
86
228
  this.networkAdapter = options.system.network ?? createNetworkStub();
229
+ this.syncBridge = createBrowserSyncBridgePayload(options.payloadLimits);
230
+ this.syncFilesystem = createSyncBridgeFilesystem(options);
87
231
  this.worker = new Worker(resolveWorkerUrl(factoryOptions.workerUrl), {
88
232
  type: "module",
89
233
  });
@@ -96,7 +240,9 @@ export class BrowserRuntimeDriver {
96
240
  permissions: serializePermissions(options.system.permissions),
97
241
  filesystem: browserSystemOptions.filesystem,
98
242
  networkEnabled: browserSystemOptions.networkEnabled,
243
+ timingMitigation: this.defaultTimingMitigation,
99
244
  payloadLimits: options.payloadLimits,
245
+ syncBridge: this.syncBridge,
100
246
  };
101
247
  this.ready = this.callWorker("init", initPayload).then(() => undefined);
102
248
  this.ready.catch(() => undefined);
@@ -110,16 +256,29 @@ export class BrowserRuntimeDriver {
110
256
  };
111
257
  }
112
258
  handleWorkerError = (event) => {
259
+ if (this.disposed) {
260
+ return;
261
+ }
113
262
  const error = event.error instanceof Error
114
263
  ? event.error
115
264
  : new Error(event.message
116
265
  ? `Browser runtime worker error: ${event.message} (${event.filename}:${event.lineno}:${event.colno})`
117
266
  : "Browser runtime worker error");
118
- this.rejectAllPending(error);
267
+ this.cleanup(error, { terminateWorker: true });
119
268
  };
120
269
  handleWorkerMessage = (event) => {
270
+ if (this.disposed) {
271
+ return;
272
+ }
121
273
  const message = event.data;
122
- if (message.type === "stdio") {
274
+ if (message.controlToken !== this.controlToken) {
275
+ return;
276
+ }
277
+ if (isSyncRequestMessage(message)) {
278
+ void this.handleSyncRequest(message);
279
+ return;
280
+ }
281
+ if (isStdioMessage(message)) {
123
282
  const pending = this.pending.get(message.requestId);
124
283
  const hook = pending?.hook ?? this.defaultOnStdio;
125
284
  if (!hook) {
@@ -133,6 +292,9 @@ export class BrowserRuntimeDriver {
133
292
  }
134
293
  return;
135
294
  }
295
+ if (!isResponseMessage(message)) {
296
+ return;
297
+ }
136
298
  const pending = this.pending.get(message.id);
137
299
  if (!pending) {
138
300
  return;
@@ -149,6 +311,37 @@ export class BrowserRuntimeDriver {
149
311
  error.code = message.error.code;
150
312
  pending.reject(error);
151
313
  };
314
+ async handleSyncRequest(message) {
315
+ const signal = new Int32Array(this.syncBridge.signalBuffer);
316
+ const data = new Uint8Array(this.syncBridge.dataBuffer);
317
+ try {
318
+ const response = await handleSyncBridgeOperation(this.syncFilesystem, message);
319
+ const bytes = createSyncBridgeResponseBytes(response, this.encoder);
320
+ if (bytes.byteLength > data.byteLength) {
321
+ const suffix = typeof message.args[0] === "string" ? ` ${message.args[0]}` : "";
322
+ throwBridgePayloadTooLarge(`${message.operation}${suffix}`, bytes.byteLength, data.byteLength);
323
+ }
324
+ data.set(bytes, 0);
325
+ Atomics.store(signal, SYNC_BRIDGE_SIGNAL_STATUS_INDEX, SYNC_BRIDGE_STATUS_OK);
326
+ Atomics.store(signal, SYNC_BRIDGE_SIGNAL_KIND_INDEX, response.kind);
327
+ Atomics.store(signal, SYNC_BRIDGE_SIGNAL_LENGTH_INDEX, bytes.byteLength);
328
+ }
329
+ catch (error) {
330
+ let bytes = this.encoder.encode(JSON.stringify(toBrowserSyncBridgeError(error)));
331
+ if (bytes.byteLength > data.byteLength) {
332
+ bytes = this.encoder.encode(JSON.stringify({
333
+ message: "Browser runtime sync bridge error exceeded shared buffer capacity",
334
+ code: SYNC_BRIDGE_PAYLOAD_LIMIT_ERROR_CODE,
335
+ }));
336
+ }
337
+ data.set(bytes, 0);
338
+ Atomics.store(signal, SYNC_BRIDGE_SIGNAL_STATUS_INDEX, SYNC_BRIDGE_STATUS_ERROR);
339
+ Atomics.store(signal, SYNC_BRIDGE_SIGNAL_KIND_INDEX, SYNC_BRIDGE_KIND_JSON);
340
+ Atomics.store(signal, SYNC_BRIDGE_SIGNAL_LENGTH_INDEX, bytes.byteLength);
341
+ }
342
+ Atomics.store(signal, SYNC_BRIDGE_SIGNAL_STATE_INDEX, SYNC_BRIDGE_SIGNAL_STATE_READY);
343
+ Atomics.notify(signal, SYNC_BRIDGE_SIGNAL_STATE_INDEX, 1);
344
+ }
152
345
  rejectAllPending(error) {
153
346
  const entries = Array.from(this.pending.values());
154
347
  this.pending.clear();
@@ -156,17 +349,68 @@ export class BrowserRuntimeDriver {
156
349
  pending.reject(error);
157
350
  }
158
351
  }
352
+ clearWorkerHandlers() {
353
+ try {
354
+ this.worker.onmessage = null;
355
+ }
356
+ catch {
357
+ // Ignore host Worker implementations with non-writable event hooks.
358
+ }
359
+ try {
360
+ this.worker.onerror = null;
361
+ }
362
+ catch {
363
+ // Ignore host Worker implementations with non-writable event hooks.
364
+ }
365
+ }
366
+ resetSyncBridgeState() {
367
+ new Int32Array(this.syncBridge.signalBuffer).fill(0);
368
+ new Uint8Array(this.syncBridge.dataBuffer).fill(0);
369
+ }
370
+ cleanup(error, options = {}) {
371
+ if (this.disposed) {
372
+ this.rejectAllPending(error);
373
+ return;
374
+ }
375
+ this.disposed = true;
376
+ this.clearWorkerHandlers();
377
+ if (options.terminateWorker) {
378
+ try {
379
+ this.worker.terminate();
380
+ }
381
+ catch {
382
+ // Ignore termination errors while tearing down a broken worker.
383
+ }
384
+ }
385
+ this.resetSyncBridgeState();
386
+ this.rejectAllPending(error);
387
+ }
159
388
  callWorker(type, payload, hook) {
160
389
  if (this.disposed) {
161
390
  return Promise.reject(new Error("Browser runtime has been disposed"));
162
391
  }
163
392
  const id = this.nextId++;
164
393
  const message = payload === undefined
165
- ? { id, type }
166
- : { id, type, payload };
394
+ ? {
395
+ controlToken: this.controlToken,
396
+ id,
397
+ type,
398
+ }
399
+ : {
400
+ controlToken: this.controlToken,
401
+ id,
402
+ type,
403
+ payload,
404
+ };
167
405
  return new Promise((resolve, reject) => {
168
406
  this.pending.set(id, { resolve, reject, hook });
169
- this.worker.postMessage(message);
407
+ try {
408
+ this.worker.postMessage(message);
409
+ }
410
+ catch (error) {
411
+ this.pending.delete(id);
412
+ reject(error);
413
+ }
170
414
  });
171
415
  }
172
416
  async run(code, filePath) {
@@ -188,13 +432,18 @@ export class BrowserRuntimeDriver {
188
432
  captureStdio: Boolean(hook),
189
433
  }, hook);
190
434
  }
435
+ async dispatchExtensionRequest(namespace, payload) {
436
+ await this.ready;
437
+ const response = await this.callWorker("extension", { namespace, payload });
438
+ return response.payload;
439
+ }
191
440
  dispose() {
192
441
  if (this.disposed) {
193
442
  return;
194
443
  }
195
- this.disposed = true;
196
- this.worker.terminate();
197
- this.rejectAllPending(new Error("Browser runtime has been disposed"));
444
+ this.cleanup(new Error("Browser runtime has been disposed"), {
445
+ terminateWorker: true,
446
+ });
198
447
  }
199
448
  async terminate() {
200
449
  this.dispose();
@@ -0,0 +1,222 @@
1
+ import { createInMemoryFileSystem, InMemoryFileSystem } from "./os-filesystem.js";
2
+ export type StdioChannel = "stdout" | "stderr";
3
+ export type TimingMitigation = "off" | "freeze";
4
+ type BodyLike = unknown;
5
+ export interface VirtualDirEntry {
6
+ name: string;
7
+ isDirectory: boolean;
8
+ isSymbolicLink?: boolean;
9
+ }
10
+ export interface VirtualStat {
11
+ mode: number;
12
+ size: number;
13
+ blocks: number;
14
+ dev: number;
15
+ rdev: number;
16
+ isDirectory: boolean;
17
+ isSymbolicLink: boolean;
18
+ atimeMs: number;
19
+ mtimeMs: number;
20
+ ctimeMs: number;
21
+ birthtimeMs: number;
22
+ ino: number;
23
+ nlink: number;
24
+ uid: number;
25
+ gid: number;
26
+ }
27
+ export interface VirtualFileSystem {
28
+ readFile(path: string): Promise<Uint8Array>;
29
+ readTextFile(path: string): Promise<string>;
30
+ readDir(path: string): Promise<string[]>;
31
+ readDirWithTypes(path: string): Promise<VirtualDirEntry[]>;
32
+ writeFile(path: string, content: string | Uint8Array): Promise<void>;
33
+ createDir(path: string): Promise<void>;
34
+ mkdir(path: string, options?: {
35
+ recursive?: boolean;
36
+ }): Promise<void>;
37
+ exists(path: string): Promise<boolean>;
38
+ stat(path: string): Promise<VirtualStat>;
39
+ removeFile(path: string): Promise<void>;
40
+ removeDir(path: string): Promise<void>;
41
+ rename(oldPath: string, newPath: string): Promise<void>;
42
+ realpath(path: string): Promise<string>;
43
+ symlink(target: string, linkPath: string): Promise<void>;
44
+ readlink(path: string): Promise<string>;
45
+ lstat(path: string): Promise<VirtualStat>;
46
+ link(oldPath: string, newPath: string): Promise<void>;
47
+ chmod(path: string, mode: number): Promise<void>;
48
+ chown(path: string, uid: number, gid: number): Promise<void>;
49
+ utimes(path: string, atime: number, mtime: number): Promise<void>;
50
+ truncate(path: string, length: number): Promise<void>;
51
+ pread(path: string, offset: number, length: number): Promise<Uint8Array>;
52
+ pwrite(path: string, offset: number, data: Uint8Array): Promise<void>;
53
+ }
54
+ export type PermissionDecision = boolean | {
55
+ allowed: boolean;
56
+ reason?: string;
57
+ } | {
58
+ allow: boolean;
59
+ reason?: string;
60
+ };
61
+ export type PermissionCheck<T = unknown> = (request: T) => PermissionDecision;
62
+ export interface Permissions {
63
+ fs?: PermissionCheck<{
64
+ path: string;
65
+ operation: string;
66
+ }>;
67
+ network?: PermissionCheck<{
68
+ url?: string;
69
+ host?: string;
70
+ port?: number;
71
+ }>;
72
+ childProcess?: PermissionCheck<{
73
+ command: string;
74
+ args: string[];
75
+ }>;
76
+ env?: PermissionCheck<{
77
+ name: string;
78
+ value: string;
79
+ }>;
80
+ }
81
+ export declare const allowAllFs: PermissionCheck;
82
+ export declare const allowAllNetwork: PermissionCheck;
83
+ export declare const allowAllChildProcess: PermissionCheck;
84
+ export declare const allowAllEnv: PermissionCheck;
85
+ export declare const allowAll: Permissions;
86
+ export interface ExecOptions {
87
+ filePath?: string;
88
+ env?: Record<string, string>;
89
+ cwd?: string;
90
+ stdin?: string;
91
+ cpuTimeLimitMs?: number;
92
+ timingMitigation?: TimingMitigation;
93
+ onStdio?: StdioHook;
94
+ }
95
+ export interface ExecResult {
96
+ code: number;
97
+ exitCode?: number;
98
+ stdout?: string;
99
+ stderr?: string;
100
+ errorMessage?: string;
101
+ }
102
+ export interface RunResult<T = unknown> {
103
+ value?: T;
104
+ code: number;
105
+ errorMessage?: string;
106
+ exports?: T;
107
+ }
108
+ export interface OSConfig {
109
+ homedir?: string;
110
+ tmpdir?: string;
111
+ }
112
+ export interface ProcessConfig {
113
+ cwd?: string;
114
+ env?: Record<string, string>;
115
+ argv?: string[];
116
+ timingMitigation?: TimingMitigation;
117
+ frozenTimeMs?: number;
118
+ }
119
+ export type StdioEvent = {
120
+ channel: StdioChannel;
121
+ message: string;
122
+ };
123
+ export type StdioHook = (event: StdioEvent) => void;
124
+ export interface CommandExecutor {
125
+ spawn(command: string, args: string[], options?: {
126
+ cwd?: string;
127
+ env?: Record<string, string>;
128
+ onStdout?: (data: Uint8Array) => void;
129
+ onStderr?: (data: Uint8Array) => void;
130
+ }): {
131
+ wait(): Promise<number>;
132
+ writeStdin(data: Uint8Array | string): void;
133
+ closeStdin(): void;
134
+ kill(signal?: number): void;
135
+ };
136
+ }
137
+ export interface NetworkAdapter {
138
+ fetch(url: string, options?: {
139
+ method?: string;
140
+ headers?: Record<string, string>;
141
+ body?: BodyLike | null;
142
+ }): Promise<{
143
+ ok: boolean;
144
+ status: number;
145
+ statusText: string;
146
+ headers: Record<string, string>;
147
+ body: string;
148
+ url: string;
149
+ redirected: boolean;
150
+ }>;
151
+ dnsLookup(hostname: string): Promise<{
152
+ address?: string;
153
+ family?: number;
154
+ error?: string;
155
+ code?: string;
156
+ }>;
157
+ httpRequest(url: string, options?: {
158
+ method?: string;
159
+ headers?: Record<string, string>;
160
+ body?: BodyLike | null;
161
+ }): Promise<{
162
+ status: number;
163
+ statusText: string;
164
+ headers: Record<string, string>;
165
+ body: string;
166
+ url: string;
167
+ }>;
168
+ }
169
+ export interface SystemDriver {
170
+ filesystem?: VirtualFileSystem;
171
+ network?: NetworkAdapter;
172
+ commandExecutor?: CommandExecutor;
173
+ permissions?: Permissions;
174
+ runtime: {
175
+ process: ProcessConfig;
176
+ os: OSConfig;
177
+ };
178
+ }
179
+ export interface RuntimeDriverOptions {
180
+ system: SystemDriver;
181
+ runtime: {
182
+ process: ProcessConfig;
183
+ os: OSConfig;
184
+ };
185
+ memoryLimit?: number;
186
+ cpuTimeLimitMs?: number;
187
+ timingMitigation?: TimingMitigation;
188
+ onStdio?: StdioHook;
189
+ payloadLimits?: {
190
+ base64TransferBytes?: number;
191
+ jsonPayloadBytes?: number;
192
+ };
193
+ }
194
+ export interface NodeRuntimeDriver {
195
+ exec(code: string, options?: ExecOptions): Promise<ExecResult>;
196
+ run<T = unknown>(code: string, filePath?: string): Promise<RunResult<T>>;
197
+ dispose(): void;
198
+ terminate?(): Promise<void>;
199
+ }
200
+ export interface NodeRuntimeDriverFactory {
201
+ createRuntimeDriver(options: RuntimeDriverOptions): NodeRuntimeDriver;
202
+ }
203
+ export declare function filterEnv(env: Record<string, string> | undefined, permissions?: Permissions): Record<string, string>;
204
+ export declare function createEnosysError(operation: string): Error;
205
+ export declare function createFsStub(): VirtualFileSystem;
206
+ export declare function createNetworkStub(): NetworkAdapter;
207
+ export declare function createCommandExecutorStub(): CommandExecutor;
208
+ export declare function wrapFileSystem(filesystem: VirtualFileSystem, permissions?: Permissions): VirtualFileSystem;
209
+ export declare function wrapNetworkAdapter(adapter: NetworkAdapter, permissions?: Permissions): NetworkAdapter;
210
+ export declare function mkdir(filesystem: VirtualFileSystem, path: string, options?: {
211
+ recursive?: boolean;
212
+ } | boolean): Promise<void>;
213
+ export declare function loadFile(path: string, filesystem: VirtualFileSystem): Promise<string | null>;
214
+ export declare function resolveModule(specifier: string, fromPath: string, filesystem: VirtualFileSystem, _mode?: "require" | "import"): Promise<string | null>;
215
+ export declare function isESM(code: string, filePath?: string): boolean;
216
+ export declare function transformDynamicImport(code: string): string;
217
+ export declare const POLYFILL_CODE_MAP: Record<string, string>;
218
+ export declare function exposeCustomGlobal(name: string, value: unknown): void;
219
+ export declare function exposeMutableRuntimeStateGlobal(name: string, value: unknown): void;
220
+ export declare function getIsolateRuntimeSource(id: string): string;
221
+ export declare function getRequireSetupCode(): string;
222
+ export { createInMemoryFileSystem, InMemoryFileSystem };