@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.
package/dist/worker.js CHANGED
@@ -1,12 +1,16 @@
1
1
  import { transform } from "sucrase";
2
- import { getRequireSetupCode, createCommandExecutorStub, createFsStub, createNetworkStub, filterEnv, wrapFileSystem, wrapNetworkAdapter, createInMemoryFileSystem, isESM, transformDynamicImport, getIsolateRuntimeSource, POLYFILL_CODE_MAP, loadFile, resolveModule, mkdir, exposeCustomGlobal, exposeMutableRuntimeStateGlobal, } from "@secure-exec/core";
3
- import { createBrowserNetworkAdapter, createOpfsFileSystem, } from "./driver.js";
2
+ import { createBrowserNetworkAdapter } from "./driver.js";
4
3
  import { validatePermissionSource } from "./permission-validation.js";
5
- let filesystem = null;
4
+ import { createCommandExecutorStub, createNetworkStub, exposeCustomGlobal, exposeMutableRuntimeStateGlobal, filterEnv, getIsolateRuntimeSource, getRequireSetupCode, isESM, POLYFILL_CODE_MAP, transformDynamicImport, wrapNetworkAdapter, } from "./runtime.js";
5
+ import { assertBrowserSyncBridgeSupport, SYNC_BRIDGE_KIND_BINARY, SYNC_BRIDGE_KIND_JSON, SYNC_BRIDGE_KIND_NONE, SYNC_BRIDGE_KIND_TEXT, SYNC_BRIDGE_SIGNAL_KIND_INDEX, SYNC_BRIDGE_SIGNAL_LENGTH_INDEX, SYNC_BRIDGE_SIGNAL_STATE_IDLE, SYNC_BRIDGE_SIGNAL_STATE_INDEX, SYNC_BRIDGE_SIGNAL_STATUS_INDEX, SYNC_BRIDGE_STATUS_ERROR, } from "./sync-bridge.js";
6
6
  let networkAdapter = null;
7
7
  let commandExecutor = null;
8
8
  let permissions;
9
9
  let initialized = false;
10
+ let controlToken = null;
11
+ let runtimeTimingMitigation = "freeze";
12
+ let runtimeProcessConfig = null;
13
+ let activeProcessRequestId = null;
10
14
  const dynamicImportCache = new Map();
11
15
  const MAX_ERROR_MESSAGE_CHARS = 8192;
12
16
  const MAX_STDIO_MESSAGE_CHARS = 8192;
@@ -20,9 +24,209 @@ const PAYLOAD_LIMIT_ERROR_CODE = "ERR_SANDBOX_PAYLOAD_TOO_LARGE";
20
24
  let base64TransferLimitBytes = DEFAULT_BASE64_TRANSFER_BYTES;
21
25
  let jsonPayloadLimitBytes = DEFAULT_JSON_PAYLOAD_BYTES;
22
26
  const encoder = new TextEncoder();
27
+ const decoder = new TextDecoder();
28
+ // biome-ignore lint/security/noGlobalEval: the browser worker intentionally evaluates isolated runtime source strings.
29
+ const globalEval = eval;
30
+ const SHARED_ARRAY_BUFFER_FREEZE_KEYS = [
31
+ "byteLength",
32
+ "slice",
33
+ "grow",
34
+ "maxByteLength",
35
+ "growable",
36
+ ];
37
+ const timingGlobals = {
38
+ captured: false,
39
+ sharedArrayBufferPrototypeDescriptors: new Map(),
40
+ };
23
41
  function getUtf8ByteLength(text) {
24
42
  return encoder.encode(text).byteLength;
25
43
  }
44
+ function getRequiredControlToken() {
45
+ if (!controlToken) {
46
+ throw new Error("Browser runtime worker control channel is not initialized");
47
+ }
48
+ return controlToken;
49
+ }
50
+ function captureTimingGlobals() {
51
+ if (timingGlobals.captured) {
52
+ return;
53
+ }
54
+ timingGlobals.captured = true;
55
+ timingGlobals.dateDescriptor = Object.getOwnPropertyDescriptor(globalThis, "Date");
56
+ timingGlobals.dateValue = globalThis.Date;
57
+ timingGlobals.performanceDescriptor = Object.getOwnPropertyDescriptor(globalThis, "performance");
58
+ timingGlobals.performanceValue = globalThis.performance;
59
+ timingGlobals.sharedArrayBufferDescriptor = Object.getOwnPropertyDescriptor(globalThis, "SharedArrayBuffer");
60
+ timingGlobals.sharedArrayBufferValue = globalThis.SharedArrayBuffer;
61
+ const sharedArrayBufferCtor = globalThis.SharedArrayBuffer;
62
+ if (typeof sharedArrayBufferCtor !== "function") {
63
+ return;
64
+ }
65
+ const prototype = sharedArrayBufferCtor.prototype;
66
+ for (const key of SHARED_ARRAY_BUFFER_FREEZE_KEYS) {
67
+ timingGlobals.sharedArrayBufferPrototypeDescriptors.set(key, Object.getOwnPropertyDescriptor(prototype, key));
68
+ }
69
+ }
70
+ function restoreGlobalProperty(name, descriptor) {
71
+ if (descriptor) {
72
+ try {
73
+ Object.defineProperty(globalThis, name, descriptor);
74
+ return;
75
+ }
76
+ catch {
77
+ if ("value" in descriptor) {
78
+ globalThis[name] = descriptor.value;
79
+ return;
80
+ }
81
+ }
82
+ }
83
+ Reflect.deleteProperty(globalThis, name);
84
+ }
85
+ function restoreSharedArrayBufferPrototype() {
86
+ const sharedArrayBufferCtor = timingGlobals.sharedArrayBufferValue;
87
+ if (typeof sharedArrayBufferCtor !== "function") {
88
+ return;
89
+ }
90
+ const prototype = sharedArrayBufferCtor.prototype;
91
+ for (const key of SHARED_ARRAY_BUFFER_FREEZE_KEYS) {
92
+ const descriptor = timingGlobals.sharedArrayBufferPrototypeDescriptors.get(key);
93
+ try {
94
+ if (descriptor) {
95
+ Object.defineProperty(prototype, key, descriptor);
96
+ }
97
+ else {
98
+ delete prototype[key];
99
+ }
100
+ }
101
+ catch {
102
+ // Ignore non-configurable SharedArrayBuffer prototype properties.
103
+ }
104
+ }
105
+ }
106
+ function restoreTimingMitigationOff() {
107
+ captureTimingGlobals();
108
+ restoreGlobalProperty("Date", timingGlobals.dateDescriptor);
109
+ restoreGlobalProperty("performance", timingGlobals.performanceDescriptor);
110
+ restoreSharedArrayBufferPrototype();
111
+ restoreGlobalProperty("SharedArrayBuffer", timingGlobals.sharedArrayBufferDescriptor);
112
+ if (typeof globalThis.performance === "undefined" ||
113
+ globalThis.performance === null) {
114
+ Object.defineProperty(globalThis, "performance", {
115
+ value: {
116
+ now: () => Date.now(),
117
+ },
118
+ configurable: true,
119
+ writable: true,
120
+ });
121
+ }
122
+ }
123
+ function applyTimingMitigation(timingMitigation, frozenTimeMs) {
124
+ captureTimingGlobals();
125
+ restoreTimingMitigationOff();
126
+ if (timingMitigation !== "freeze") {
127
+ return undefined;
128
+ }
129
+ const frozenTimeValue = typeof frozenTimeMs === "number" && Number.isFinite(frozenTimeMs)
130
+ ? Math.trunc(frozenTimeMs)
131
+ : Date.now();
132
+ const originalDate = timingGlobals.dateValue ?? timingGlobals.dateDescriptor?.value ?? Date;
133
+ const frozenDateNow = () => frozenTimeValue;
134
+ const FrozenDate = function (...args) {
135
+ if (new.target) {
136
+ if (args.length === 0) {
137
+ return new originalDate(frozenTimeValue);
138
+ }
139
+ return new originalDate(...args);
140
+ }
141
+ return originalDate();
142
+ };
143
+ Object.defineProperty(FrozenDate, "prototype", {
144
+ value: originalDate.prototype,
145
+ writable: false,
146
+ configurable: false,
147
+ });
148
+ Object.defineProperty(FrozenDate, "now", {
149
+ value: frozenDateNow,
150
+ configurable: true,
151
+ writable: false,
152
+ });
153
+ FrozenDate.parse = originalDate.parse;
154
+ FrozenDate.UTC = originalDate.UTC;
155
+ try {
156
+ Object.defineProperty(globalThis, "Date", {
157
+ value: FrozenDate,
158
+ configurable: true,
159
+ writable: false,
160
+ });
161
+ }
162
+ catch {
163
+ globalThis.Date = FrozenDate;
164
+ }
165
+ const frozenPerformance = Object.create(null);
166
+ const originalPerformance = timingGlobals.performanceValue;
167
+ if (typeof originalPerformance !== "undefined" &&
168
+ originalPerformance !== null) {
169
+ const source = originalPerformance;
170
+ for (const key of Object.getOwnPropertyNames(Object.getPrototypeOf(originalPerformance) ?? originalPerformance)) {
171
+ if (key === "now") {
172
+ continue;
173
+ }
174
+ try {
175
+ const value = source[key];
176
+ frozenPerformance[key] =
177
+ typeof value === "function" ? value.bind(originalPerformance) : value;
178
+ }
179
+ catch {
180
+ // Ignore performance accessors that throw in this host.
181
+ }
182
+ }
183
+ }
184
+ Object.defineProperty(frozenPerformance, "now", {
185
+ value: () => 0,
186
+ configurable: true,
187
+ writable: false,
188
+ });
189
+ Object.freeze(frozenPerformance);
190
+ try {
191
+ Object.defineProperty(globalThis, "performance", {
192
+ value: frozenPerformance,
193
+ configurable: true,
194
+ writable: false,
195
+ });
196
+ }
197
+ catch {
198
+ globalThis.performance = frozenPerformance;
199
+ }
200
+ const sharedArrayBufferCtor = timingGlobals.sharedArrayBufferValue;
201
+ if (typeof sharedArrayBufferCtor === "function") {
202
+ const prototype = sharedArrayBufferCtor.prototype;
203
+ for (const key of SHARED_ARRAY_BUFFER_FREEZE_KEYS) {
204
+ try {
205
+ Object.defineProperty(prototype, key, {
206
+ get() {
207
+ throw new TypeError("SharedArrayBuffer is not available in sandbox");
208
+ },
209
+ configurable: true,
210
+ });
211
+ }
212
+ catch {
213
+ // Ignore non-configurable SharedArrayBuffer prototype properties.
214
+ }
215
+ }
216
+ }
217
+ try {
218
+ Object.defineProperty(globalThis, "SharedArrayBuffer", {
219
+ value: undefined,
220
+ configurable: true,
221
+ writable: false,
222
+ enumerable: false,
223
+ });
224
+ }
225
+ catch {
226
+ Reflect.deleteProperty(globalThis, "SharedArrayBuffer");
227
+ }
228
+ return frozenTimeValue;
229
+ }
26
230
  function assertPayloadByteLength(payloadLabel, actualBytes, maxBytes) {
27
231
  if (actualBytes <= maxBytes)
28
232
  return;
@@ -33,7 +237,6 @@ function assertPayloadByteLength(payloadLabel, actualBytes, maxBytes) {
33
237
  function assertTextPayloadSize(payloadLabel, text, maxBytes) {
34
238
  assertPayloadByteLength(payloadLabel, getUtf8ByteLength(text), maxBytes);
35
239
  }
36
- const dynamicImportModule = new Function("specifier", "return import(specifier);");
37
240
  function boundErrorMessage(message) {
38
241
  if (message.length <= MAX_ERROR_MESSAGE_CHARS) {
39
242
  return message;
@@ -98,13 +301,204 @@ function makeApplyPromise(fn) {
98
301
  },
99
302
  };
100
303
  }
304
+ function normalizeTextEncoding(options) {
305
+ if (typeof options === "string") {
306
+ return options;
307
+ }
308
+ if (options && typeof options === "object" && "encoding" in options) {
309
+ const encoding = options.encoding;
310
+ return typeof encoding === "string" ? encoding : null;
311
+ }
312
+ return null;
313
+ }
314
+ function toBinaryView(data) {
315
+ if (data instanceof Uint8Array) {
316
+ return data;
317
+ }
318
+ if (ArrayBuffer.isView(data)) {
319
+ return new Uint8Array(data.buffer, data.byteOffset, data.byteLength);
320
+ }
321
+ if (data instanceof ArrayBuffer) {
322
+ return new Uint8Array(data);
323
+ }
324
+ return new TextEncoder().encode(String(data));
325
+ }
326
+ function toNodeBuffer(bytes) {
327
+ if (typeof Buffer === "function") {
328
+ return Buffer.from(bytes);
329
+ }
330
+ return bytes;
331
+ }
332
+ function createStats(stat) {
333
+ return {
334
+ ...stat,
335
+ isFile: () => !stat.isDirectory && !stat.isSymbolicLink,
336
+ isDirectory: () => stat.isDirectory,
337
+ isSymbolicLink: () => stat.isSymbolicLink,
338
+ };
339
+ }
340
+ function createDirent(entry) {
341
+ return {
342
+ name: entry.name,
343
+ isFile: () => !entry.isDirectory && !entry.isSymbolicLink,
344
+ isDirectory: () => entry.isDirectory,
345
+ isSymbolicLink: () => Boolean(entry.isSymbolicLink),
346
+ };
347
+ }
348
+ function createFsModule(syncBridge) {
349
+ const readFileSync = (path, options) => {
350
+ const encoding = normalizeTextEncoding(options);
351
+ if (encoding) {
352
+ return syncBridge.requestText("fs.readFile", [path]);
353
+ }
354
+ return toNodeBuffer(syncBridge.requestBinary("fs.readFileBinary", [path]));
355
+ };
356
+ const writeFileSync = (path, content) => {
357
+ if (typeof content === "string") {
358
+ syncBridge.requestVoid("fs.writeFile", [path, content]);
359
+ return;
360
+ }
361
+ syncBridge.requestVoid("fs.writeFileBinary", [path, toBinaryView(content)]);
362
+ };
363
+ const mkdirSync = (path, options) => {
364
+ const recursive = typeof options === "boolean" ? options : (options?.recursive ?? true);
365
+ if (recursive) {
366
+ syncBridge.requestVoid("fs.mkdir", [path]);
367
+ return;
368
+ }
369
+ syncBridge.requestVoid("fs.createDir", [path]);
370
+ };
371
+ const readdirSync = (path, options) => {
372
+ const entries = syncBridge.requestJson("fs.readDir", [
373
+ path,
374
+ ]);
375
+ if (options?.withFileTypes) {
376
+ return entries.map((entry) => createDirent(entry));
377
+ }
378
+ return entries.map((entry) => entry.name);
379
+ };
380
+ const statSync = (path) => createStats(syncBridge.requestJson("fs.stat", [path]));
381
+ const lstatSync = (path) => createStats(syncBridge.requestJson("fs.lstat", [path]));
382
+ const promises = {
383
+ readFile(path, options) {
384
+ return Promise.resolve(readFileSync(path, options));
385
+ },
386
+ writeFile(path, content) {
387
+ writeFileSync(path, content);
388
+ return Promise.resolve();
389
+ },
390
+ mkdir(path, options) {
391
+ mkdirSync(path, options);
392
+ return Promise.resolve();
393
+ },
394
+ readdir(path, options) {
395
+ return Promise.resolve(readdirSync(path, options));
396
+ },
397
+ stat(path) {
398
+ return Promise.resolve(statSync(path));
399
+ },
400
+ lstat(path) {
401
+ return Promise.resolve(lstatSync(path));
402
+ },
403
+ unlink(path) {
404
+ syncBridge.requestVoid("fs.unlink", [path]);
405
+ return Promise.resolve();
406
+ },
407
+ rmdir(path) {
408
+ syncBridge.requestVoid("fs.rmdir", [path]);
409
+ return Promise.resolve();
410
+ },
411
+ rm(path) {
412
+ syncBridge.requestVoid("fs.unlink", [path]);
413
+ return Promise.resolve();
414
+ },
415
+ rename(oldPath, newPath) {
416
+ syncBridge.requestVoid("fs.rename", [oldPath, newPath]);
417
+ return Promise.resolve();
418
+ },
419
+ realpath(path) {
420
+ return Promise.resolve(syncBridge.requestText("fs.realpath", [path]));
421
+ },
422
+ readlink(path) {
423
+ return Promise.resolve(syncBridge.requestText("fs.readlink", [path]));
424
+ },
425
+ symlink(target, path) {
426
+ syncBridge.requestVoid("fs.symlink", [target, path]);
427
+ return Promise.resolve();
428
+ },
429
+ link(existingPath, newPath) {
430
+ syncBridge.requestVoid("fs.link", [existingPath, newPath]);
431
+ return Promise.resolve();
432
+ },
433
+ chmod(path, mode) {
434
+ syncBridge.requestVoid("fs.chmod", [path, mode]);
435
+ return Promise.resolve();
436
+ },
437
+ truncate(path, length = 0) {
438
+ syncBridge.requestVoid("fs.truncate", [path, length]);
439
+ return Promise.resolve();
440
+ },
441
+ };
442
+ return {
443
+ readFileSync,
444
+ writeFileSync,
445
+ mkdirSync,
446
+ readdirSync,
447
+ existsSync(path) {
448
+ return syncBridge.requestJson("fs.exists", [path]);
449
+ },
450
+ statSync,
451
+ lstatSync,
452
+ unlinkSync(path) {
453
+ syncBridge.requestVoid("fs.unlink", [path]);
454
+ },
455
+ rmdirSync(path) {
456
+ syncBridge.requestVoid("fs.rmdir", [path]);
457
+ },
458
+ rmSync(path) {
459
+ syncBridge.requestVoid("fs.unlink", [path]);
460
+ },
461
+ renameSync(oldPath, newPath) {
462
+ syncBridge.requestVoid("fs.rename", [oldPath, newPath]);
463
+ },
464
+ realpathSync(path) {
465
+ return syncBridge.requestText("fs.realpath", [path]);
466
+ },
467
+ readlinkSync(path) {
468
+ return syncBridge.requestText("fs.readlink", [path]);
469
+ },
470
+ symlinkSync(target, path) {
471
+ syncBridge.requestVoid("fs.symlink", [target, path]);
472
+ },
473
+ linkSync(existingPath, newPath) {
474
+ syncBridge.requestVoid("fs.link", [existingPath, newPath]);
475
+ },
476
+ chmodSync(path, mode) {
477
+ syncBridge.requestVoid("fs.chmod", [path, mode]);
478
+ },
479
+ truncateSync(path, length = 0) {
480
+ syncBridge.requestVoid("fs.truncate", [path, length]);
481
+ },
482
+ promises,
483
+ };
484
+ }
101
485
  // Save real postMessage before sandbox code can replace it
102
486
  const _realPostMessage = self.postMessage.bind(self);
103
487
  function postResponse(message) {
104
- _realPostMessage(message);
488
+ _realPostMessage({
489
+ controlToken: getRequiredControlToken(),
490
+ ...message,
491
+ });
492
+ }
493
+ function postSyncRequest(message) {
494
+ _realPostMessage({
495
+ controlToken: getRequiredControlToken(),
496
+ ...message,
497
+ });
105
498
  }
106
499
  function postStdio(requestId, channel, message) {
107
500
  const payload = {
501
+ controlToken: getRequiredControlToken(),
108
502
  type: "stdio",
109
503
  requestId,
110
504
  channel,
@@ -174,6 +568,87 @@ function emitStdio(requestId, channel, args) {
174
568
  const message = boundStdioMessage(args.map((arg) => formatConsoleValue(arg)).join(" "));
175
569
  postStdio(requestId, channel, message);
176
570
  }
571
+ function createSyncBridgeClient(payload) {
572
+ const signal = new Int32Array(payload.signalBuffer);
573
+ const data = new Uint8Array(payload.dataBuffer);
574
+ let nextRequestId = 1;
575
+ const timeoutMs = payload.timeoutMs ?? 30_000;
576
+ function readBytes(length) {
577
+ if (length <= 0) {
578
+ return new Uint8Array(0);
579
+ }
580
+ return data.slice(0, length);
581
+ }
582
+ function requestRaw(operation, args) {
583
+ Atomics.store(signal, SYNC_BRIDGE_SIGNAL_STATE_INDEX, SYNC_BRIDGE_SIGNAL_STATE_IDLE);
584
+ Atomics.store(signal, SYNC_BRIDGE_SIGNAL_STATUS_INDEX, 0);
585
+ Atomics.store(signal, SYNC_BRIDGE_SIGNAL_KIND_INDEX, SYNC_BRIDGE_KIND_NONE);
586
+ Atomics.store(signal, SYNC_BRIDGE_SIGNAL_LENGTH_INDEX, 0);
587
+ postSyncRequest({
588
+ type: "sync-request",
589
+ requestId: nextRequestId++,
590
+ operation,
591
+ args,
592
+ });
593
+ while (true) {
594
+ const result = Atomics.wait(signal, SYNC_BRIDGE_SIGNAL_STATE_INDEX, SYNC_BRIDGE_SIGNAL_STATE_IDLE, timeoutMs);
595
+ if (result !== "timed-out") {
596
+ break;
597
+ }
598
+ throw new Error(`Browser runtime sync bridge timed out while handling ${operation}`);
599
+ }
600
+ const status = Atomics.load(signal, SYNC_BRIDGE_SIGNAL_STATUS_INDEX);
601
+ const kind = Atomics.load(signal, SYNC_BRIDGE_SIGNAL_KIND_INDEX);
602
+ const length = Atomics.load(signal, SYNC_BRIDGE_SIGNAL_LENGTH_INDEX);
603
+ const bytes = readBytes(length);
604
+ Atomics.store(signal, SYNC_BRIDGE_SIGNAL_STATE_INDEX, SYNC_BRIDGE_SIGNAL_STATE_IDLE);
605
+ if (status === SYNC_BRIDGE_STATUS_ERROR) {
606
+ const errorPayload = JSON.parse(decoder.decode(bytes));
607
+ const error = new Error(errorPayload.message);
608
+ if (errorPayload.code) {
609
+ error.code = errorPayload.code;
610
+ }
611
+ throw error;
612
+ }
613
+ return { kind, bytes };
614
+ }
615
+ return {
616
+ requestVoid(operation, args) {
617
+ requestRaw(operation, args);
618
+ },
619
+ requestText(operation, args) {
620
+ const result = requestRaw(operation, args);
621
+ if (result.kind !== SYNC_BRIDGE_KIND_TEXT) {
622
+ throw new Error(`Expected text response from ${operation}, received kind ${result.kind}`);
623
+ }
624
+ return decoder.decode(result.bytes);
625
+ },
626
+ requestNullableText(operation, args) {
627
+ const result = requestRaw(operation, args);
628
+ if (result.kind === SYNC_BRIDGE_KIND_NONE) {
629
+ return null;
630
+ }
631
+ if (result.kind !== SYNC_BRIDGE_KIND_TEXT) {
632
+ throw new Error(`Expected text response from ${operation}, received kind ${result.kind}`);
633
+ }
634
+ return decoder.decode(result.bytes);
635
+ },
636
+ requestBinary(operation, args) {
637
+ const result = requestRaw(operation, args);
638
+ if (result.kind !== SYNC_BRIDGE_KIND_BINARY) {
639
+ throw new Error(`Expected binary response from ${operation}, received kind ${result.kind}`);
640
+ }
641
+ return result.bytes;
642
+ },
643
+ requestJson(operation, args) {
644
+ const result = requestRaw(operation, args);
645
+ if (result.kind !== SYNC_BRIDGE_KIND_JSON) {
646
+ throw new Error(`Expected JSON response from ${operation}, received kind ${result.kind}`);
647
+ }
648
+ return JSON.parse(decoder.decode(result.bytes));
649
+ },
650
+ };
651
+ }
177
652
  /**
178
653
  * Initialize the worker-side runtime: set up filesystem, network, bridge
179
654
  * globals, and load the bridge bundle. Called once before any exec/run.
@@ -181,14 +656,18 @@ function emitStdio(requestId, channel, args) {
181
656
  async function initRuntime(payload) {
182
657
  if (initialized)
183
658
  return;
659
+ assertBrowserSyncBridgeSupport();
660
+ captureTimingGlobals();
661
+ if (!payload.syncBridge) {
662
+ throw new Error("Browser runtime sync bridge is required for filesystem and module loading parity");
663
+ }
184
664
  permissions = revivePermissions(payload.permissions);
665
+ const syncBridge = createSyncBridgeClient(payload.syncBridge);
185
666
  // Apply payload limits (use defaults if not configured)
186
- base64TransferLimitBytes = payload.payloadLimits?.base64TransferBytes ?? DEFAULT_BASE64_TRANSFER_BYTES;
187
- jsonPayloadLimitBytes = payload.payloadLimits?.jsonPayloadBytes ?? DEFAULT_JSON_PAYLOAD_BYTES;
188
- const baseFs = payload.filesystem === "memory"
189
- ? createInMemoryFileSystem()
190
- : await createOpfsFileSystem();
191
- filesystem = wrapFileSystem(baseFs, permissions);
667
+ base64TransferLimitBytes =
668
+ payload.payloadLimits?.base64TransferBytes ?? DEFAULT_BASE64_TRANSFER_BYTES;
669
+ jsonPayloadLimitBytes =
670
+ payload.payloadLimits?.jsonPayloadBytes ?? DEFAULT_JSON_PAYLOAD_BYTES;
192
671
  if (payload.networkEnabled) {
193
672
  networkAdapter = wrapNetworkAdapter(createBrowserNetworkAdapter(), permissions);
194
673
  }
@@ -196,53 +675,58 @@ async function initRuntime(payload) {
196
675
  networkAdapter = createNetworkStub();
197
676
  }
198
677
  commandExecutor = createCommandExecutorStub();
199
- const fsOps = filesystem ?? createFsStub();
200
678
  const processConfig = payload.processConfig ?? {};
679
+ runtimeProcessConfig = processConfig;
680
+ runtimeTimingMitigation =
681
+ payload.timingMitigation ??
682
+ processConfig.timingMitigation ??
683
+ runtimeTimingMitigation;
201
684
  processConfig.env = filterEnv(processConfig.env, permissions);
685
+ processConfig.timingMitigation = runtimeTimingMitigation;
686
+ delete processConfig.frozenTimeMs;
202
687
  exposeCustomGlobal("_processConfig", processConfig);
203
688
  exposeCustomGlobal("_osConfig", payload.osConfig ?? {});
204
689
  // Set up filesystem bridge globals before loading runtime shims.
205
- const readFileRef = makeApplySyncPromise(async (path) => {
206
- const text = await fsOps.readTextFile(path);
690
+ const readFileRef = makeApplySync((path) => {
691
+ const text = syncBridge.requestText("fs.readFile", [path]);
207
692
  assertTextPayloadSize(`fs.readFile ${path}`, text, jsonPayloadLimitBytes);
208
693
  return text;
209
694
  });
210
- const writeFileRef = makeApplySyncPromise(async (path, content) => {
211
- return fsOps.writeFile(path, content);
695
+ const writeFileRef = makeApplySync((path, content) => {
696
+ assertTextPayloadSize(`fs.writeFile ${path}`, content, jsonPayloadLimitBytes);
697
+ syncBridge.requestVoid("fs.writeFile", [path, content]);
212
698
  });
213
- const readFileBinaryRef = makeApplySyncPromise(async (path) => {
214
- const data = await fsOps.readFile(path);
699
+ const readFileBinaryRef = makeApplySync((path) => {
700
+ const data = syncBridge.requestBinary("fs.readFileBinary", [path]);
215
701
  assertPayloadByteLength(`fs.readFileBinary ${path}`, data.byteLength, base64TransferLimitBytes);
216
- return new Uint8Array(data.buffer, data.byteOffset, data.byteLength);
702
+ return data;
217
703
  });
218
- const writeFileBinaryRef = makeApplySyncPromise(async (path, binaryContent) => {
704
+ const writeFileBinaryRef = makeApplySync((path, binaryContent) => {
219
705
  assertPayloadByteLength(`fs.writeFileBinary ${path}`, binaryContent.byteLength, base64TransferLimitBytes);
220
- return fsOps.writeFile(path, binaryContent);
706
+ syncBridge.requestVoid("fs.writeFileBinary", [path, binaryContent]);
221
707
  });
222
- const readDirRef = makeApplySyncPromise(async (path) => {
223
- const entries = await fsOps.readDirWithTypes(path);
224
- const json = JSON.stringify(entries);
708
+ const readDirRef = makeApplySync((path) => {
709
+ const json = JSON.stringify(syncBridge.requestJson("fs.readDir", [path]));
225
710
  assertTextPayloadSize(`fs.readDir ${path}`, json, jsonPayloadLimitBytes);
226
711
  return json;
227
712
  });
228
- const mkdirRef = makeApplySyncPromise(async (path) => {
229
- return mkdir(fsOps, path);
713
+ const mkdirRef = makeApplySync((path) => {
714
+ syncBridge.requestVoid("fs.mkdir", [path]);
230
715
  });
231
- const rmdirRef = makeApplySyncPromise(async (path) => {
232
- return fsOps.removeDir(path);
716
+ const rmdirRef = makeApplySync((path) => {
717
+ syncBridge.requestVoid("fs.rmdir", [path]);
233
718
  });
234
- const existsRef = makeApplySyncPromise(async (path) => {
235
- return fsOps.exists(path);
719
+ const existsRef = makeApplySync((path) => {
720
+ return syncBridge.requestJson("fs.exists", [path]);
236
721
  });
237
- const statRef = makeApplySyncPromise(async (path) => {
238
- const statInfo = await fsOps.stat(path);
239
- return JSON.stringify(statInfo);
722
+ const statRef = makeApplySync((path) => {
723
+ return JSON.stringify(syncBridge.requestJson("fs.stat", [path]));
240
724
  });
241
- const unlinkRef = makeApplySyncPromise(async (path) => {
242
- return fsOps.removeFile(path);
725
+ const unlinkRef = makeApplySync((path) => {
726
+ syncBridge.requestVoid("fs.unlink", [path]);
243
727
  });
244
- const renameRef = makeApplySyncPromise(async (oldPath, newPath) => {
245
- return fsOps.rename(oldPath, newPath);
728
+ const renameRef = makeApplySync((oldPath, newPath) => {
729
+ syncBridge.requestVoid("fs.rename", [oldPath, newPath]);
246
730
  });
247
731
  exposeCustomGlobal("_fs", {
248
732
  readFile: readFileRef,
@@ -257,24 +741,33 @@ async function initRuntime(payload) {
257
741
  unlink: unlinkRef,
258
742
  rename: renameRef,
259
743
  });
260
- exposeCustomGlobal("_loadPolyfill", makeApplySyncPromise(async (moduleName) => {
744
+ exposeCustomGlobal("_loadPolyfill", (moduleName) => {
261
745
  const name = moduleName.replace(/^node:/, "");
262
746
  const polyfillMap = POLYFILL_CODE_MAP;
263
747
  return polyfillMap[name] ?? null;
264
- }));
265
- exposeCustomGlobal("_resolveModule", makeApplySyncPromise(async (request, fromDir) => {
266
- return resolveModule(request, fromDir, fsOps);
267
- }));
268
- exposeCustomGlobal("_loadFile", makeApplySyncPromise(async (path) => {
269
- const source = await loadFile(path, fsOps);
270
- if (source === null)
748
+ });
749
+ const resolveModuleSync = (request, fromDir, mode) => {
750
+ return syncBridge.requestNullableText("module.resolve", [
751
+ request,
752
+ fromDir,
753
+ mode ?? "require",
754
+ ]);
755
+ };
756
+ const loadFileSync = (path, _mode) => {
757
+ const source = syncBridge.requestNullableText("module.loadFile", [path]);
758
+ if (source === null) {
271
759
  return null;
760
+ }
272
761
  let code = source;
273
762
  if (isESM(source, path)) {
274
763
  code = transform(code, { transforms: ["imports"] }).code;
275
764
  }
276
765
  return transformDynamicImport(code);
277
- }));
766
+ };
767
+ exposeCustomGlobal("_resolveModuleSync", resolveModuleSync);
768
+ exposeCustomGlobal("_loadFileSync", loadFileSync);
769
+ exposeCustomGlobal("_resolveModule", resolveModuleSync);
770
+ exposeCustomGlobal("_loadFile", loadFileSync);
278
771
  exposeCustomGlobal("_scheduleTimer", {
279
772
  apply(_ctx, args) {
280
773
  return new Promise((resolve) => {
@@ -292,11 +785,6 @@ async function initRuntime(payload) {
292
785
  const result = await netAdapter.dnsLookup(hostname);
293
786
  return JSON.stringify(result);
294
787
  }));
295
- exposeCustomGlobal("_networkHttpRequestRaw", makeApplyPromise(async (url, optionsJson) => {
296
- const options = JSON.parse(optionsJson);
297
- const result = await netAdapter.httpRequest(url, options);
298
- return JSON.stringify(result);
299
- }));
300
788
  const execAdapter = commandExecutor ?? createCommandExecutorStub();
301
789
  let nextSessionId = 1;
302
790
  const sessions = new Map();
@@ -315,7 +803,7 @@ async function initRuntime(payload) {
315
803
  getDispatch()?.(sessionId, "stderr", data);
316
804
  },
317
805
  });
318
- proc.wait().then((code) => {
806
+ void proc.wait().then((code) => {
319
807
  getDispatch()?.(sessionId, "exit", code);
320
808
  sessions.delete(sessionId);
321
809
  });
@@ -348,50 +836,12 @@ async function initRuntime(payload) {
348
836
  const stderr = stderrChunks.map((c) => decoder.decode(c)).join("");
349
837
  return JSON.stringify({ stdout, stderr, code: exitCode });
350
838
  }));
351
- if (!("SharedArrayBuffer" in globalThis)) {
352
- class SharedArrayBufferShim {
353
- backing;
354
- constructor(length) {
355
- this.backing = new ArrayBuffer(length);
356
- }
357
- get byteLength() {
358
- return this.backing.byteLength;
359
- }
360
- get growable() {
361
- return false;
362
- }
363
- get maxByteLength() {
364
- return this.backing.byteLength;
365
- }
366
- slice(start, end) {
367
- return this.backing.slice(start, end);
368
- }
369
- }
370
- Object.defineProperty(globalThis, "SharedArrayBuffer", {
371
- value: SharedArrayBufferShim,
372
- configurable: true,
373
- writable: true,
374
- });
375
- }
376
- let bridgeModule;
377
- try {
378
- bridgeModule = await dynamicImportModule("@secure-exec/core/internal/bridge");
379
- }
380
- catch {
381
- // Vite browser tests may need source fallback.
382
- try {
383
- bridgeModule = await dynamicImportModule("@secure-exec/core/internal/bridge");
384
- }
385
- catch {
386
- throw new Error("Failed to load bridge module from @secure-exec/core");
387
- }
388
- }
389
- exposeCustomGlobal("_fsModule", bridgeModule.default);
390
- eval(getIsolateRuntimeSource("globalExposureHelpers"));
839
+ exposeCustomGlobal("_fsModule", createFsModule(syncBridge));
391
840
  exposeMutableRuntimeStateGlobal("_moduleCache", {});
392
841
  exposeMutableRuntimeStateGlobal("_pendingModules", {});
393
842
  exposeMutableRuntimeStateGlobal("_currentModule", { dirname: "/" });
394
- eval(getRequireSetupCode());
843
+ globalEval(getRequireSetupCode());
844
+ ensureProcessGlobal();
395
845
  // Block dangerous Web APIs that bypass bridge permission checks
396
846
  const dangerousApis = [
397
847
  "XMLHttpRequest",
@@ -437,7 +887,7 @@ function resetModuleState(cwd) {
437
887
  exposeMutableRuntimeStateGlobal("_currentModule", { dirname: cwd });
438
888
  }
439
889
  function setDynamicImportFallback() {
440
- exposeMutableRuntimeStateGlobal("__dynamicImport", function (specifier) {
890
+ exposeMutableRuntimeStateGlobal("__dynamicImport", (specifier) => {
441
891
  const cached = dynamicImportCache.get(specifier);
442
892
  if (cached)
443
893
  return Promise.resolve(cached);
@@ -447,13 +897,286 @@ function setDynamicImportFallback() {
447
897
  throw new Error("require is not available in browser runtime");
448
898
  }
449
899
  const mod = runtimeRequire(specifier);
450
- return Promise.resolve({ default: mod, ...mod });
900
+ return Promise.resolve({
901
+ default: mod,
902
+ ...mod,
903
+ });
451
904
  }
452
905
  catch (e) {
453
906
  return Promise.reject(new Error(`Cannot dynamically import '${specifier}': ${String(e)}`));
454
907
  }
455
908
  });
456
909
  }
910
+ function toProcessChunk(value, encoding) {
911
+ if (encoding) {
912
+ return value;
913
+ }
914
+ return encoder.encode(value);
915
+ }
916
+ function normalizeProcessOutputChunk(chunk) {
917
+ if (typeof chunk === "string") {
918
+ return chunk;
919
+ }
920
+ if (chunk instanceof Uint8Array) {
921
+ return decoder.decode(chunk);
922
+ }
923
+ if (ArrayBuffer.isView(chunk)) {
924
+ return decoder.decode(new Uint8Array(chunk.buffer, chunk.byteOffset, chunk.byteLength));
925
+ }
926
+ if (chunk instanceof ArrayBuffer) {
927
+ return decoder.decode(new Uint8Array(chunk));
928
+ }
929
+ return String(chunk);
930
+ }
931
+ function emitProcessStdio(channel, chunk) {
932
+ if (activeProcessRequestId === null) {
933
+ return true;
934
+ }
935
+ emitStdio(activeProcessRequestId, channel, [
936
+ normalizeProcessOutputChunk(chunk),
937
+ ]);
938
+ return true;
939
+ }
940
+ function createBrowserProcess() {
941
+ let cwd = "/";
942
+ let stdinData = "";
943
+ let stdinPosition = 0;
944
+ let stdinEnded = false;
945
+ let stdinFlushQueued = false;
946
+ const stdinListeners = Object.create(null);
947
+ const stdinOnceListeners = Object.create(null);
948
+ const emitStdinListeners = (event, value) => {
949
+ const listeners = [
950
+ ...(stdinListeners[event] ?? []),
951
+ ...(stdinOnceListeners[event] ?? []),
952
+ ];
953
+ stdinOnceListeners[event] = [];
954
+ for (const listener of listeners) {
955
+ listener(value);
956
+ }
957
+ return listeners.length > 0;
958
+ };
959
+ const clearStdinListeners = () => {
960
+ for (const key of Object.keys(stdinListeners)) {
961
+ stdinListeners[key] = [];
962
+ }
963
+ for (const key of Object.keys(stdinOnceListeners)) {
964
+ stdinOnceListeners[key] = [];
965
+ }
966
+ };
967
+ const flushStdin = () => {
968
+ stdinFlushQueued = false;
969
+ if (stdin.paused || stdinEnded) {
970
+ return;
971
+ }
972
+ if (stdinPosition < stdinData.length) {
973
+ const chunk = stdinData.slice(stdinPosition);
974
+ stdinPosition = stdinData.length;
975
+ emitStdinListeners("data", toProcessChunk(chunk, stdin.encoding));
976
+ }
977
+ if (!stdinEnded) {
978
+ stdinEnded = true;
979
+ emitStdinListeners("end");
980
+ emitStdinListeners("close");
981
+ }
982
+ };
983
+ const scheduleStdinFlush = () => {
984
+ if (stdinFlushQueued) {
985
+ return;
986
+ }
987
+ stdinFlushQueued = true;
988
+ queueMicrotask(flushStdin);
989
+ };
990
+ const stdin = {
991
+ readable: true,
992
+ paused: true,
993
+ encoding: null,
994
+ isRaw: false,
995
+ read(size) {
996
+ if (stdinPosition >= stdinData.length) {
997
+ return null;
998
+ }
999
+ const chunk = size
1000
+ ? stdinData.slice(stdinPosition, stdinPosition + size)
1001
+ : stdinData.slice(stdinPosition);
1002
+ stdinPosition += chunk.length;
1003
+ return toProcessChunk(chunk, stdin.encoding);
1004
+ },
1005
+ on(event, listener) {
1006
+ if (!stdinListeners[event]) {
1007
+ stdinListeners[event] = [];
1008
+ }
1009
+ stdinListeners[event].push(listener);
1010
+ if (event === "data" && stdin.paused) {
1011
+ stdin.resume();
1012
+ }
1013
+ return stdin;
1014
+ },
1015
+ once(event, listener) {
1016
+ if (!stdinOnceListeners[event]) {
1017
+ stdinOnceListeners[event] = [];
1018
+ }
1019
+ stdinOnceListeners[event].push(listener);
1020
+ if (event === "data" && stdin.paused) {
1021
+ stdin.resume();
1022
+ }
1023
+ return stdin;
1024
+ },
1025
+ off(event, listener) {
1026
+ if (!stdinListeners[event]) {
1027
+ return stdin;
1028
+ }
1029
+ stdinListeners[event] = stdinListeners[event].filter((candidate) => candidate !== listener);
1030
+ return stdin;
1031
+ },
1032
+ removeListener(event, listener) {
1033
+ return stdin.off(event, listener);
1034
+ },
1035
+ emit(event, value) {
1036
+ return emitStdinListeners(event, value);
1037
+ },
1038
+ pause() {
1039
+ stdin.paused = true;
1040
+ return stdin;
1041
+ },
1042
+ resume() {
1043
+ stdin.paused = false;
1044
+ scheduleStdinFlush();
1045
+ return stdin;
1046
+ },
1047
+ setEncoding(encoding) {
1048
+ stdin.encoding = encoding;
1049
+ return stdin;
1050
+ },
1051
+ setRawMode(mode) {
1052
+ stdin.isRaw = mode;
1053
+ return stdin;
1054
+ },
1055
+ get isTTY() {
1056
+ return false;
1057
+ },
1058
+ async *[Symbol.asyncIterator]() {
1059
+ const remaining = stdinData.slice(stdinPosition);
1060
+ for (const line of remaining.split("\n")) {
1061
+ if (line.length > 0) {
1062
+ yield line;
1063
+ }
1064
+ }
1065
+ },
1066
+ };
1067
+ const processBridge = {
1068
+ browser: true,
1069
+ env: {},
1070
+ argv: ["node"],
1071
+ argv0: "node",
1072
+ pid: 1,
1073
+ ppid: 0,
1074
+ platform: "browser",
1075
+ version: "v22.0.0",
1076
+ versions: {
1077
+ node: "22.0.0",
1078
+ },
1079
+ stdin,
1080
+ stdout: {
1081
+ isTTY: false,
1082
+ write(chunk) {
1083
+ return emitProcessStdio("stdout", chunk);
1084
+ },
1085
+ },
1086
+ stderr: {
1087
+ isTTY: false,
1088
+ write(chunk) {
1089
+ return emitProcessStdio("stderr", chunk);
1090
+ },
1091
+ },
1092
+ exitCode: 0,
1093
+ cwd: () => cwd,
1094
+ chdir: (nextCwd) => {
1095
+ cwd = String(nextCwd);
1096
+ },
1097
+ nextTick: (callback, ...args) => {
1098
+ queueMicrotask(() => callback(...args));
1099
+ },
1100
+ exit(code) {
1101
+ const exitCode = typeof code === "number" ? code : (processBridge.exitCode ?? 0);
1102
+ processBridge.exitCode = exitCode;
1103
+ throw new Error(`process.exit(${exitCode})`);
1104
+ },
1105
+ on() {
1106
+ return processBridge;
1107
+ },
1108
+ once() {
1109
+ return processBridge;
1110
+ },
1111
+ off() {
1112
+ return processBridge;
1113
+ },
1114
+ removeListener() {
1115
+ return processBridge;
1116
+ },
1117
+ emit() {
1118
+ return false;
1119
+ },
1120
+ __secureExecRefreshProcess(nextConfig) {
1121
+ clearStdinListeners();
1122
+ stdinData = typeof nextConfig?.stdin === "string" ? nextConfig.stdin : "";
1123
+ stdinPosition = 0;
1124
+ stdinEnded = false;
1125
+ stdinFlushQueued = false;
1126
+ stdin.paused = true;
1127
+ stdin.encoding = null;
1128
+ stdin.isRaw = false;
1129
+ processBridge.exitCode = 0;
1130
+ processBridge.env =
1131
+ nextConfig?.env && typeof nextConfig.env === "object"
1132
+ ? { ...nextConfig.env }
1133
+ : {};
1134
+ if (typeof nextConfig?.cwd === "string") {
1135
+ cwd = nextConfig.cwd;
1136
+ }
1137
+ processBridge.argv = Array.isArray(nextConfig?.argv)
1138
+ ? nextConfig.argv.map((value) => String(value))
1139
+ : ["node"];
1140
+ processBridge.argv0 = processBridge.argv[0] ?? "node";
1141
+ if (typeof nextConfig?.platform === "string") {
1142
+ processBridge.platform = nextConfig.platform;
1143
+ }
1144
+ if (typeof nextConfig?.version === "string") {
1145
+ processBridge.version = nextConfig.version;
1146
+ processBridge.versions.node = nextConfig.version.replace(/^v/, "");
1147
+ }
1148
+ if (typeof nextConfig?.pid === "number") {
1149
+ processBridge.pid = nextConfig.pid;
1150
+ }
1151
+ if (typeof nextConfig?.ppid === "number") {
1152
+ processBridge.ppid = nextConfig.ppid;
1153
+ }
1154
+ },
1155
+ };
1156
+ return processBridge;
1157
+ }
1158
+ function getRuntimeProcess() {
1159
+ const proc = globalThis.process;
1160
+ if (!proc || typeof proc !== "object") {
1161
+ return undefined;
1162
+ }
1163
+ return proc;
1164
+ }
1165
+ function refreshRuntimeProcess() {
1166
+ const proc = getRuntimeProcess();
1167
+ const refresh = proc?.__secureExecRefreshProcess;
1168
+ if (typeof refresh === "function") {
1169
+ refresh(runtimeProcessConfig);
1170
+ }
1171
+ }
1172
+ function ensureProcessGlobal() {
1173
+ if (getRuntimeProcess()) {
1174
+ refreshRuntimeProcess();
1175
+ return;
1176
+ }
1177
+ exposeMutableRuntimeStateGlobal("process", createBrowserProcess());
1178
+ refreshRuntimeProcess();
1179
+ }
457
1180
  function captureConsole(requestId, captureStdio) {
458
1181
  const original = console;
459
1182
  if (!captureStdio) {
@@ -483,25 +1206,48 @@ function captureConsole(requestId, captureStdio) {
483
1206
  },
484
1207
  };
485
1208
  }
486
- function updateProcessConfig(options) {
487
- const proc = globalThis.process;
1209
+ function updateProcessConfig(options, timingMitigation, frozenTimeMs) {
1210
+ if (runtimeProcessConfig) {
1211
+ runtimeProcessConfig.timingMitigation = timingMitigation;
1212
+ if (frozenTimeMs === undefined) {
1213
+ delete runtimeProcessConfig.frozenTimeMs;
1214
+ }
1215
+ else {
1216
+ runtimeProcessConfig.frozenTimeMs = frozenTimeMs;
1217
+ }
1218
+ runtimeProcessConfig.stdin = options?.stdin ?? "";
1219
+ if (options?.env) {
1220
+ const filtered = filterEnv(options.env, permissions);
1221
+ const currentEnv = runtimeProcessConfig.env && typeof runtimeProcessConfig.env === "object"
1222
+ ? runtimeProcessConfig.env
1223
+ : {};
1224
+ runtimeProcessConfig.env = { ...currentEnv, ...filtered };
1225
+ }
1226
+ }
1227
+ refreshRuntimeProcess();
1228
+ const proc = getRuntimeProcess();
488
1229
  if (!proc)
489
1230
  return;
490
- if (options?.cwd && typeof proc.chdir === "function") {
491
- proc.chdir(options.cwd);
1231
+ proc.exitCode = 0;
1232
+ proc.timingMitigation = timingMitigation;
1233
+ if (frozenTimeMs === undefined) {
1234
+ delete proc.frozenTimeMs;
492
1235
  }
493
- if (options?.env) {
494
- const filtered = filterEnv(options.env, permissions);
495
- const currentEnv = proc.env && typeof proc.env === "object"
496
- ? proc.env
497
- : {};
498
- proc.env = { ...currentEnv, ...filtered };
1236
+ else {
1237
+ proc.frozenTimeMs = frozenTimeMs;
499
1238
  }
500
- if (options?.stdin !== undefined) {
501
- exposeMutableRuntimeStateGlobal("_stdinData", options.stdin);
502
- exposeMutableRuntimeStateGlobal("_stdinPosition", 0);
503
- exposeMutableRuntimeStateGlobal("_stdinEnded", false);
504
- exposeMutableRuntimeStateGlobal("_stdinFlowMode", false);
1239
+ if (options?.cwd && typeof proc.chdir === "function") {
1240
+ exposeMutableRuntimeStateGlobal("__runtimeProcessCwdOverride", options.cwd);
1241
+ globalEval(getIsolateRuntimeSource("overrideProcessCwd"));
1242
+ try {
1243
+ proc.chdir(options.cwd);
1244
+ }
1245
+ catch (error) {
1246
+ if (!(error instanceof Error &&
1247
+ error.message.includes("process.chdir() is not supported in workers"))) {
1248
+ throw error;
1249
+ }
1250
+ }
505
1251
  }
506
1252
  }
507
1253
  /**
@@ -510,8 +1256,12 @@ function updateProcessConfig(options) {
510
1256
  */
511
1257
  async function execScript(requestId, code, options, captureStdio = false) {
512
1258
  resetModuleState(options?.cwd ?? "/");
513
- updateProcessConfig(options);
1259
+ const timingMitigation = options?.timingMitigation ?? runtimeTimingMitigation;
1260
+ const frozenTimeMs = applyTimingMitigation(timingMitigation);
1261
+ updateProcessConfig(options, timingMitigation, frozenTimeMs);
514
1262
  setDynamicImportFallback();
1263
+ const previousProcessRequestId = activeProcessRequestId;
1264
+ activeProcessRequestId = captureStdio ? requestId : null;
515
1265
  const { restore } = captureConsole(requestId, captureStdio);
516
1266
  try {
517
1267
  let transformed = code;
@@ -524,7 +1274,8 @@ async function execScript(requestId, code, options, captureStdio = false) {
524
1274
  exposeMutableRuntimeStateGlobal("exports", moduleRef.exports);
525
1275
  if (options?.filePath) {
526
1276
  const dirname = options.filePath.includes("/")
527
- ? options.filePath.substring(0, options.filePath.lastIndexOf("/")) || "/"
1277
+ ? options.filePath.substring(0, options.filePath.lastIndexOf("/")) ||
1278
+ "/"
528
1279
  : "/";
529
1280
  exposeMutableRuntimeStateGlobal("__filename", options.filePath);
530
1281
  exposeMutableRuntimeStateGlobal("__dirname", dirname);
@@ -535,10 +1286,13 @@ async function execScript(requestId, code, options, captureStdio = false) {
535
1286
  }
536
1287
  // Await the eval result so async IIFEs / top-level promise expressions
537
1288
  // resolve before we check for active handles.
538
- const evalResult = eval(transformed);
539
- if (evalResult && typeof evalResult === "object" && typeof evalResult.then === "function") {
1289
+ const evalResult = globalEval(transformed);
1290
+ if (evalResult &&
1291
+ typeof evalResult === "object" &&
1292
+ typeof evalResult.then === "function") {
540
1293
  await evalResult;
541
1294
  }
1295
+ await Promise.resolve();
542
1296
  const waitForActiveHandles = globalThis
543
1297
  ._waitForActiveHandles;
544
1298
  if (typeof waitForActiveHandles === "function") {
@@ -565,6 +1319,7 @@ async function execScript(requestId, code, options, captureStdio = false) {
565
1319
  };
566
1320
  }
567
1321
  finally {
1322
+ activeProcessRequestId = previousProcessRequestId;
568
1323
  restore();
569
1324
  }
570
1325
  }
@@ -580,8 +1335,24 @@ self.onmessage = async (event) => {
580
1335
  const message = event.data;
581
1336
  try {
582
1337
  if (message.type === "init") {
1338
+ if (typeof message.controlToken !== "string" ||
1339
+ message.controlToken.length === 0) {
1340
+ return;
1341
+ }
1342
+ if (controlToken && message.controlToken !== controlToken) {
1343
+ return;
1344
+ }
1345
+ controlToken = message.controlToken;
583
1346
  await initRuntime(message.payload);
584
- postResponse({ type: "response", id: message.id, ok: true, result: true });
1347
+ postResponse({
1348
+ type: "response",
1349
+ id: message.id,
1350
+ ok: true,
1351
+ result: true,
1352
+ });
1353
+ return;
1354
+ }
1355
+ if (!controlToken || message.controlToken !== controlToken) {
585
1356
  return;
586
1357
  }
587
1358
  if (!initialized) {
@@ -597,8 +1368,18 @@ self.onmessage = async (event) => {
597
1368
  postResponse({ type: "response", id: message.id, ok: true, result });
598
1369
  return;
599
1370
  }
1371
+ if (message.type === "extension") {
1372
+ const error = new Error(`Browser worker extension dispatch is not implemented for namespace ${message.payload.namespace}`);
1373
+ error.code = "ERR_SECURE_EXEC_BROWSER_EXTENSION_UNSUPPORTED";
1374
+ throw error;
1375
+ }
600
1376
  if (message.type === "dispose") {
601
- postResponse({ type: "response", id: message.id, ok: true, result: true });
1377
+ postResponse({
1378
+ type: "response",
1379
+ id: message.id,
1380
+ ok: true,
1381
+ result: true,
1382
+ });
602
1383
  close();
603
1384
  }
604
1385
  }