@secure-exec/browser 0.2.0-rc.2 → 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/README.md +4 -5
- package/dist/driver.d.ts +10 -4
- package/dist/driver.js +18 -3
- package/dist/index.d.ts +6 -5
- package/dist/index.js +2 -2
- package/dist/os-filesystem.d.ts +4 -4
- package/dist/os-filesystem.js +28 -11
- package/dist/runtime-driver.d.ts +11 -3
- package/dist/runtime-driver.js +271 -22
- package/dist/runtime.d.ts +222 -0
- package/dist/runtime.js +377 -0
- package/dist/sync-bridge.d.ts +46 -0
- package/dist/sync-bridge.js +49 -0
- package/dist/worker-adapter.js +1 -0
- package/dist/worker-protocol.d.ts +33 -10
- package/dist/worker.js +897 -116
- package/package.json +79 -66
- package/LICENSE +0 -191
package/dist/worker.js
CHANGED
|
@@ -1,12 +1,16 @@
|
|
|
1
1
|
import { transform } from "sucrase";
|
|
2
|
-
import {
|
|
3
|
-
import { createBrowserNetworkAdapter, createOpfsFileSystem, } from "./driver.js";
|
|
2
|
+
import { createBrowserNetworkAdapter } from "./driver.js";
|
|
4
3
|
import { validatePermissionSource } from "./permission-validation.js";
|
|
5
|
-
|
|
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(
|
|
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 =
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
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 =
|
|
206
|
-
const text =
|
|
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 =
|
|
211
|
-
|
|
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 =
|
|
214
|
-
const data =
|
|
699
|
+
const readFileBinaryRef = makeApplySync((path) => {
|
|
700
|
+
const data = syncBridge.requestBinary("fs.readFileBinary", [path]);
|
|
215
701
|
assertPayloadByteLength(`fs.readFileBinary ${path}`, data.byteLength, base64TransferLimitBytes);
|
|
216
|
-
return
|
|
702
|
+
return data;
|
|
217
703
|
});
|
|
218
|
-
const writeFileBinaryRef =
|
|
704
|
+
const writeFileBinaryRef = makeApplySync((path, binaryContent) => {
|
|
219
705
|
assertPayloadByteLength(`fs.writeFileBinary ${path}`, binaryContent.byteLength, base64TransferLimitBytes);
|
|
220
|
-
|
|
706
|
+
syncBridge.requestVoid("fs.writeFileBinary", [path, binaryContent]);
|
|
221
707
|
});
|
|
222
|
-
const readDirRef =
|
|
223
|
-
const
|
|
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 =
|
|
229
|
-
|
|
713
|
+
const mkdirRef = makeApplySync((path) => {
|
|
714
|
+
syncBridge.requestVoid("fs.mkdir", [path]);
|
|
230
715
|
});
|
|
231
|
-
const rmdirRef =
|
|
232
|
-
|
|
716
|
+
const rmdirRef = makeApplySync((path) => {
|
|
717
|
+
syncBridge.requestVoid("fs.rmdir", [path]);
|
|
233
718
|
});
|
|
234
|
-
const existsRef =
|
|
235
|
-
return
|
|
719
|
+
const existsRef = makeApplySync((path) => {
|
|
720
|
+
return syncBridge.requestJson("fs.exists", [path]);
|
|
236
721
|
});
|
|
237
|
-
const statRef =
|
|
238
|
-
|
|
239
|
-
return JSON.stringify(statInfo);
|
|
722
|
+
const statRef = makeApplySync((path) => {
|
|
723
|
+
return JSON.stringify(syncBridge.requestJson("fs.stat", [path]));
|
|
240
724
|
});
|
|
241
|
-
const unlinkRef =
|
|
242
|
-
|
|
725
|
+
const unlinkRef = makeApplySync((path) => {
|
|
726
|
+
syncBridge.requestVoid("fs.unlink", [path]);
|
|
243
727
|
});
|
|
244
|
-
const renameRef =
|
|
245
|
-
|
|
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",
|
|
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
|
-
|
|
266
|
-
return
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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",
|
|
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({
|
|
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
|
-
|
|
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
|
-
|
|
491
|
-
|
|
1231
|
+
proc.exitCode = 0;
|
|
1232
|
+
proc.timingMitigation = timingMitigation;
|
|
1233
|
+
if (frozenTimeMs === undefined) {
|
|
1234
|
+
delete proc.frozenTimeMs;
|
|
492
1235
|
}
|
|
493
|
-
|
|
494
|
-
|
|
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?.
|
|
501
|
-
exposeMutableRuntimeStateGlobal("
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
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
|
-
|
|
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 =
|
|
539
|
-
if (evalResult &&
|
|
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({
|
|
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({
|
|
1377
|
+
postResponse({
|
|
1378
|
+
type: "response",
|
|
1379
|
+
id: message.id,
|
|
1380
|
+
ok: true,
|
|
1381
|
+
result: true,
|
|
1382
|
+
});
|
|
602
1383
|
close();
|
|
603
1384
|
}
|
|
604
1385
|
}
|