@secure-exec/browser 0.0.0-agentos-dylib-base.edaa4a4
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 +6 -0
- package/dist/child-process-bridge.d.ts +25 -0
- package/dist/child-process-bridge.js +50 -0
- package/dist/converged-base64.d.ts +2 -0
- package/dist/converged-base64.js +41 -0
- package/dist/converged-dgram-bridge.d.ts +11 -0
- package/dist/converged-dgram-bridge.js +147 -0
- package/dist/converged-driver-setup.d.ts +22 -0
- package/dist/converged-driver-setup.js +72 -0
- package/dist/converged-execution-host-bridge.d.ts +7 -0
- package/dist/converged-execution-host-bridge.js +85 -0
- package/dist/converged-executor-session.d.ts +60 -0
- package/dist/converged-executor-session.js +127 -0
- package/dist/converged-fs-bridge.d.ts +42 -0
- package/dist/converged-fs-bridge.js +245 -0
- package/dist/converged-module-servicer.d.ts +8 -0
- package/dist/converged-module-servicer.js +79 -0
- package/dist/converged-net-bridge.d.ts +28 -0
- package/dist/converged-net-bridge.js +155 -0
- package/dist/converged-permissions.d.ts +9 -0
- package/dist/converged-permissions.js +46 -0
- package/dist/converged-sync-bridge-handler.d.ts +47 -0
- package/dist/converged-sync-bridge-handler.js +140 -0
- package/dist/converged-sync-bridge-router.d.ts +33 -0
- package/dist/converged-sync-bridge-router.js +41 -0
- package/dist/driver.d.ts +91 -0
- package/dist/driver.js +386 -0
- package/dist/encoding.d.ts +4 -0
- package/dist/encoding.js +102 -0
- package/dist/generated/util-polyfill.d.ts +1 -0
- package/dist/generated/util-polyfill.js +2 -0
- package/dist/index.d.ts +9 -0
- package/dist/index.js +5 -0
- package/dist/kernel-backed-filesystem.d.ts +33 -0
- package/dist/kernel-backed-filesystem.js +205 -0
- package/dist/os-filesystem.d.ts +47 -0
- package/dist/os-filesystem.js +409 -0
- package/dist/permission-validation.d.ts +15 -0
- package/dist/permission-validation.js +62 -0
- package/dist/root-filesystem-from-vfs.d.ts +13 -0
- package/dist/root-filesystem-from-vfs.js +95 -0
- package/dist/runtime-driver.d.ts +66 -0
- package/dist/runtime-driver.js +611 -0
- package/dist/runtime.d.ts +248 -0
- package/dist/runtime.js +2296 -0
- package/dist/sidecar-wasm-module.d.ts +62 -0
- package/dist/sidecar-wasm-module.js +28 -0
- package/dist/sidecar-worker-protocol.d.ts +14 -0
- package/dist/sidecar-worker-protocol.js +9 -0
- package/dist/sidecar-worker.d.ts +19 -0
- package/dist/sidecar-worker.js +63 -0
- package/dist/signals.d.ts +13 -0
- package/dist/signals.js +89 -0
- package/dist/sync-bridge.d.ts +50 -0
- package/dist/sync-bridge.js +93 -0
- package/dist/wasi-polyfill.d.ts +1 -0
- package/dist/wasi-polyfill.js +2154 -0
- package/dist/worker-adapter.d.ts +21 -0
- package/dist/worker-adapter.js +41 -0
- package/dist/worker-protocol.d.ts +104 -0
- package/dist/worker-protocol.js +1 -0
- package/dist/worker-sidecar-client.d.ts +71 -0
- package/dist/worker-sidecar-client.js +152 -0
- package/dist/worker.d.ts +1 -0
- package/dist/worker.js +2125 -0
- package/package.json +111 -0
package/dist/worker.js
ADDED
|
@@ -0,0 +1,2125 @@
|
|
|
1
|
+
import { hmac as nobleHmac } from "@noble/hashes/hmac.js";
|
|
2
|
+
import { cbc as nobleAesCbc, ctr as nobleAesCtr, gcm as nobleAesGcm } from "@noble/ciphers/aes.js";
|
|
3
|
+
import { md5, sha1 } from "@noble/hashes/legacy.js";
|
|
4
|
+
import { pbkdf2 as noblePbkdf2 } from "@noble/hashes/pbkdf2.js";
|
|
5
|
+
import { sha224, sha256, sha384, sha512 } from "@noble/hashes/sha2.js";
|
|
6
|
+
import { scrypt as nobleScrypt } from "@noble/hashes/scrypt.js";
|
|
7
|
+
import { transform } from "sucrase";
|
|
8
|
+
import { createBrowserNetworkAdapter } from "./driver.js";
|
|
9
|
+
import { base64ToBytes, toUint8Array } from "./encoding.js";
|
|
10
|
+
import { validatePermissionSource } from "./permission-validation.js";
|
|
11
|
+
import { defaultSignalExitCode, signalNumberForEvent } from "./signals.js";
|
|
12
|
+
import { createNetworkStub, exposeCustomGlobal, exposeMutableRuntimeStateGlobal, filterEnv, getIsolateRuntimeSource, getRequireSetupCode, isESM, POLYFILL_CODE_MAP, transformDynamicImport, wrapNetworkAdapter, } from "./runtime.js";
|
|
13
|
+
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";
|
|
14
|
+
let networkAdapter = null;
|
|
15
|
+
let permissions;
|
|
16
|
+
let initialized = false;
|
|
17
|
+
let controlToken = null;
|
|
18
|
+
let runtimeTimingMitigation = "freeze";
|
|
19
|
+
let runtimeProcessConfig = null;
|
|
20
|
+
let activeProcessRequestId = null;
|
|
21
|
+
let activeExecutionId = null;
|
|
22
|
+
let activeCaptureStdio = false;
|
|
23
|
+
let activeSyncBridge = null;
|
|
24
|
+
const pendingExecutionSignals = new Map();
|
|
25
|
+
const dynamicImportCache = new Map();
|
|
26
|
+
const MAX_ERROR_MESSAGE_CHARS = 8192;
|
|
27
|
+
const MAX_STDIO_MESSAGE_CHARS = 8192;
|
|
28
|
+
// Payload size defaults matching the Node runtime path
|
|
29
|
+
const DEFAULT_BASE64_TRANSFER_BYTES = 16 * 1024 * 1024;
|
|
30
|
+
const DEFAULT_JSON_PAYLOAD_BYTES = 4 * 1024 * 1024;
|
|
31
|
+
const PAYLOAD_LIMIT_ERROR_CODE = "ERR_SANDBOX_PAYLOAD_TOO_LARGE";
|
|
32
|
+
const DEFAULT_SCRYPT_COST = 16_384;
|
|
33
|
+
const DEFAULT_SCRYPT_BLOCK_SIZE = 8;
|
|
34
|
+
const DEFAULT_SCRYPT_PARALLELIZATION = 1;
|
|
35
|
+
let base64TransferLimitBytes = DEFAULT_BASE64_TRANSFER_BYTES;
|
|
36
|
+
let jsonPayloadLimitBytes = DEFAULT_JSON_PAYLOAD_BYTES;
|
|
37
|
+
const encoder = new TextEncoder();
|
|
38
|
+
const decoder = new TextDecoder();
|
|
39
|
+
// biome-ignore lint/security/noGlobalEval: the browser worker intentionally evaluates isolated runtime source strings.
|
|
40
|
+
const globalEval = eval;
|
|
41
|
+
const SHARED_ARRAY_BUFFER_FREEZE_KEYS = [
|
|
42
|
+
"byteLength",
|
|
43
|
+
"slice",
|
|
44
|
+
"grow",
|
|
45
|
+
"maxByteLength",
|
|
46
|
+
"growable",
|
|
47
|
+
];
|
|
48
|
+
const timingGlobals = {
|
|
49
|
+
captured: false,
|
|
50
|
+
sharedArrayBufferPrototypeDescriptors: new Map(),
|
|
51
|
+
};
|
|
52
|
+
function getUtf8ByteLength(text) {
|
|
53
|
+
return encoder.encode(text).byteLength;
|
|
54
|
+
}
|
|
55
|
+
function getRequiredControlToken() {
|
|
56
|
+
if (!controlToken) {
|
|
57
|
+
throw new Error("Browser runtime worker control channel is not initialized");
|
|
58
|
+
}
|
|
59
|
+
return controlToken;
|
|
60
|
+
}
|
|
61
|
+
function captureTimingGlobals() {
|
|
62
|
+
if (timingGlobals.captured) {
|
|
63
|
+
return;
|
|
64
|
+
}
|
|
65
|
+
timingGlobals.captured = true;
|
|
66
|
+
timingGlobals.dateDescriptor = Object.getOwnPropertyDescriptor(globalThis, "Date");
|
|
67
|
+
timingGlobals.dateValue = globalThis.Date;
|
|
68
|
+
timingGlobals.performanceDescriptor = Object.getOwnPropertyDescriptor(globalThis, "performance");
|
|
69
|
+
timingGlobals.performanceValue = globalThis.performance;
|
|
70
|
+
timingGlobals.sharedArrayBufferDescriptor = Object.getOwnPropertyDescriptor(globalThis, "SharedArrayBuffer");
|
|
71
|
+
timingGlobals.sharedArrayBufferValue = globalThis.SharedArrayBuffer;
|
|
72
|
+
const sharedArrayBufferCtor = globalThis.SharedArrayBuffer;
|
|
73
|
+
if (typeof sharedArrayBufferCtor !== "function") {
|
|
74
|
+
return;
|
|
75
|
+
}
|
|
76
|
+
const prototype = sharedArrayBufferCtor.prototype;
|
|
77
|
+
for (const key of SHARED_ARRAY_BUFFER_FREEZE_KEYS) {
|
|
78
|
+
timingGlobals.sharedArrayBufferPrototypeDescriptors.set(key, Object.getOwnPropertyDescriptor(prototype, key));
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
function restoreGlobalProperty(name, descriptor) {
|
|
82
|
+
if (descriptor) {
|
|
83
|
+
try {
|
|
84
|
+
Object.defineProperty(globalThis, name, descriptor);
|
|
85
|
+
return;
|
|
86
|
+
}
|
|
87
|
+
catch {
|
|
88
|
+
if ("value" in descriptor) {
|
|
89
|
+
globalThis[name] = descriptor.value;
|
|
90
|
+
return;
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
Reflect.deleteProperty(globalThis, name);
|
|
95
|
+
}
|
|
96
|
+
function restoreSharedArrayBufferPrototype() {
|
|
97
|
+
const sharedArrayBufferCtor = timingGlobals.sharedArrayBufferValue;
|
|
98
|
+
if (typeof sharedArrayBufferCtor !== "function") {
|
|
99
|
+
return;
|
|
100
|
+
}
|
|
101
|
+
const prototype = sharedArrayBufferCtor.prototype;
|
|
102
|
+
for (const key of SHARED_ARRAY_BUFFER_FREEZE_KEYS) {
|
|
103
|
+
const descriptor = timingGlobals.sharedArrayBufferPrototypeDescriptors.get(key);
|
|
104
|
+
try {
|
|
105
|
+
if (descriptor) {
|
|
106
|
+
Object.defineProperty(prototype, key, descriptor);
|
|
107
|
+
}
|
|
108
|
+
else {
|
|
109
|
+
delete prototype[key];
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
catch {
|
|
113
|
+
// Ignore non-configurable SharedArrayBuffer prototype properties.
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
function restoreTimingMitigationOff() {
|
|
118
|
+
captureTimingGlobals();
|
|
119
|
+
restoreGlobalProperty("Date", timingGlobals.dateDescriptor);
|
|
120
|
+
restoreGlobalProperty("performance", timingGlobals.performanceDescriptor);
|
|
121
|
+
restoreSharedArrayBufferPrototype();
|
|
122
|
+
restoreGlobalProperty("SharedArrayBuffer", timingGlobals.sharedArrayBufferDescriptor);
|
|
123
|
+
if (typeof globalThis.performance === "undefined" ||
|
|
124
|
+
globalThis.performance === null) {
|
|
125
|
+
Object.defineProperty(globalThis, "performance", {
|
|
126
|
+
value: {
|
|
127
|
+
now: () => Date.now(),
|
|
128
|
+
},
|
|
129
|
+
configurable: true,
|
|
130
|
+
writable: true,
|
|
131
|
+
});
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
function applyTimingMitigation(timingMitigation, frozenTimeMs) {
|
|
135
|
+
captureTimingGlobals();
|
|
136
|
+
restoreTimingMitigationOff();
|
|
137
|
+
if (timingMitigation !== "freeze") {
|
|
138
|
+
return undefined;
|
|
139
|
+
}
|
|
140
|
+
const frozenTimeValue = typeof frozenTimeMs === "number" && Number.isFinite(frozenTimeMs)
|
|
141
|
+
? Math.trunc(frozenTimeMs)
|
|
142
|
+
: Date.now();
|
|
143
|
+
const originalDate = timingGlobals.dateValue ?? timingGlobals.dateDescriptor?.value ?? Date;
|
|
144
|
+
const frozenDateNow = () => frozenTimeValue;
|
|
145
|
+
const FrozenDate = function (...args) {
|
|
146
|
+
if (new.target) {
|
|
147
|
+
if (args.length === 0) {
|
|
148
|
+
return new originalDate(frozenTimeValue);
|
|
149
|
+
}
|
|
150
|
+
return new originalDate(...args);
|
|
151
|
+
}
|
|
152
|
+
return originalDate();
|
|
153
|
+
};
|
|
154
|
+
Object.defineProperty(FrozenDate, "prototype", {
|
|
155
|
+
value: originalDate.prototype,
|
|
156
|
+
writable: false,
|
|
157
|
+
configurable: false,
|
|
158
|
+
});
|
|
159
|
+
Object.defineProperty(FrozenDate, "now", {
|
|
160
|
+
value: frozenDateNow,
|
|
161
|
+
configurable: true,
|
|
162
|
+
writable: false,
|
|
163
|
+
});
|
|
164
|
+
FrozenDate.parse = originalDate.parse;
|
|
165
|
+
FrozenDate.UTC = originalDate.UTC;
|
|
166
|
+
try {
|
|
167
|
+
Object.defineProperty(globalThis, "Date", {
|
|
168
|
+
value: FrozenDate,
|
|
169
|
+
configurable: true,
|
|
170
|
+
writable: false,
|
|
171
|
+
});
|
|
172
|
+
}
|
|
173
|
+
catch {
|
|
174
|
+
globalThis.Date = FrozenDate;
|
|
175
|
+
}
|
|
176
|
+
const frozenPerformance = Object.create(null);
|
|
177
|
+
const originalPerformance = timingGlobals.performanceValue;
|
|
178
|
+
if (typeof originalPerformance !== "undefined" &&
|
|
179
|
+
originalPerformance !== null) {
|
|
180
|
+
const source = originalPerformance;
|
|
181
|
+
for (const key of Object.getOwnPropertyNames(Object.getPrototypeOf(originalPerformance) ?? originalPerformance)) {
|
|
182
|
+
if (key === "now") {
|
|
183
|
+
continue;
|
|
184
|
+
}
|
|
185
|
+
try {
|
|
186
|
+
const value = source[key];
|
|
187
|
+
frozenPerformance[key] =
|
|
188
|
+
typeof value === "function" ? value.bind(originalPerformance) : value;
|
|
189
|
+
}
|
|
190
|
+
catch {
|
|
191
|
+
// Ignore performance accessors that throw in this host.
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
Object.defineProperty(frozenPerformance, "now", {
|
|
196
|
+
value: () => 0,
|
|
197
|
+
configurable: true,
|
|
198
|
+
writable: false,
|
|
199
|
+
});
|
|
200
|
+
Object.freeze(frozenPerformance);
|
|
201
|
+
try {
|
|
202
|
+
Object.defineProperty(globalThis, "performance", {
|
|
203
|
+
value: frozenPerformance,
|
|
204
|
+
configurable: true,
|
|
205
|
+
writable: false,
|
|
206
|
+
});
|
|
207
|
+
}
|
|
208
|
+
catch {
|
|
209
|
+
globalThis.performance = frozenPerformance;
|
|
210
|
+
}
|
|
211
|
+
const sharedArrayBufferCtor = timingGlobals.sharedArrayBufferValue;
|
|
212
|
+
if (typeof sharedArrayBufferCtor === "function") {
|
|
213
|
+
const prototype = sharedArrayBufferCtor.prototype;
|
|
214
|
+
for (const key of SHARED_ARRAY_BUFFER_FREEZE_KEYS) {
|
|
215
|
+
try {
|
|
216
|
+
Object.defineProperty(prototype, key, {
|
|
217
|
+
get() {
|
|
218
|
+
throw new TypeError("SharedArrayBuffer is not available in sandbox");
|
|
219
|
+
},
|
|
220
|
+
configurable: true,
|
|
221
|
+
});
|
|
222
|
+
}
|
|
223
|
+
catch {
|
|
224
|
+
// Ignore non-configurable SharedArrayBuffer prototype properties.
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
try {
|
|
229
|
+
Object.defineProperty(globalThis, "SharedArrayBuffer", {
|
|
230
|
+
value: undefined,
|
|
231
|
+
configurable: true,
|
|
232
|
+
writable: false,
|
|
233
|
+
enumerable: false,
|
|
234
|
+
});
|
|
235
|
+
}
|
|
236
|
+
catch {
|
|
237
|
+
Reflect.deleteProperty(globalThis, "SharedArrayBuffer");
|
|
238
|
+
}
|
|
239
|
+
return frozenTimeValue;
|
|
240
|
+
}
|
|
241
|
+
function assertPayloadByteLength(payloadLabel, actualBytes, maxBytes) {
|
|
242
|
+
if (actualBytes <= maxBytes)
|
|
243
|
+
return;
|
|
244
|
+
const error = new Error(`[${PAYLOAD_LIMIT_ERROR_CODE}] ${payloadLabel}: payload is ${actualBytes} bytes, limit is ${maxBytes} bytes`);
|
|
245
|
+
error.code = PAYLOAD_LIMIT_ERROR_CODE;
|
|
246
|
+
throw error;
|
|
247
|
+
}
|
|
248
|
+
function assertTextPayloadSize(payloadLabel, text, maxBytes) {
|
|
249
|
+
assertPayloadByteLength(payloadLabel, getUtf8ByteLength(text), maxBytes);
|
|
250
|
+
}
|
|
251
|
+
function parseScryptOptions(value) {
|
|
252
|
+
if (value == null)
|
|
253
|
+
return {};
|
|
254
|
+
if (typeof value === "string") {
|
|
255
|
+
const parsed = JSON.parse(value);
|
|
256
|
+
return typeof parsed === "object" && parsed !== null
|
|
257
|
+
? parsed
|
|
258
|
+
: {};
|
|
259
|
+
}
|
|
260
|
+
return typeof value === "object" ? value : {};
|
|
261
|
+
}
|
|
262
|
+
function normalizeScryptPositiveInteger(value, fallback, label) {
|
|
263
|
+
const normalized = value == null ? fallback : Number(value);
|
|
264
|
+
if (!Number.isInteger(normalized) || normalized <= 0) {
|
|
265
|
+
throw new Error(`crypto.scrypt ${label} must be a positive integer`);
|
|
266
|
+
}
|
|
267
|
+
return normalized;
|
|
268
|
+
}
|
|
269
|
+
function normalizeScryptOptions(value, keyLength) {
|
|
270
|
+
const options = parseScryptOptions(value);
|
|
271
|
+
const length = Number(keyLength);
|
|
272
|
+
if (!Number.isInteger(length) || length < 0) {
|
|
273
|
+
throw new Error("crypto.scrypt key length must be a non-negative integer");
|
|
274
|
+
}
|
|
275
|
+
const cost = normalizeScryptPositiveInteger(options.cost ?? options.N, DEFAULT_SCRYPT_COST, "cost");
|
|
276
|
+
if ((cost & (cost - 1)) !== 0) {
|
|
277
|
+
throw new Error("crypto.scrypt cost must be a positive power of two");
|
|
278
|
+
}
|
|
279
|
+
return {
|
|
280
|
+
N: cost,
|
|
281
|
+
r: normalizeScryptPositiveInteger(options.blockSize ?? options.r, DEFAULT_SCRYPT_BLOCK_SIZE, "block size"),
|
|
282
|
+
p: normalizeScryptPositiveInteger(options.parallelization ?? options.p, DEFAULT_SCRYPT_PARALLELIZATION, "parallelization"),
|
|
283
|
+
dkLen: length,
|
|
284
|
+
};
|
|
285
|
+
}
|
|
286
|
+
const BROWSER_CRYPTO_HASHES = {
|
|
287
|
+
md5,
|
|
288
|
+
sha1,
|
|
289
|
+
sha224,
|
|
290
|
+
sha256,
|
|
291
|
+
sha384,
|
|
292
|
+
sha512,
|
|
293
|
+
};
|
|
294
|
+
function normalizeCryptoHashName(algorithm) {
|
|
295
|
+
const normalized = String(algorithm).trim().toLowerCase().replace(/[-_]/g, "");
|
|
296
|
+
if (Object.hasOwn(BROWSER_CRYPTO_HASHES, normalized)) {
|
|
297
|
+
return normalized;
|
|
298
|
+
}
|
|
299
|
+
throw new Error(`Unsupported browser crypto digest algorithm: ${algorithm}`);
|
|
300
|
+
}
|
|
301
|
+
const RSA_PKCS1_DIGEST_PREFIXES = {
|
|
302
|
+
md5: "3020300c06082a864886f70d020505000410",
|
|
303
|
+
sha1: "3021300906052b0e03021a05000414",
|
|
304
|
+
sha224: "302d300d06096086480165030402040500041c",
|
|
305
|
+
sha256: "3031300d060960864801650304020105000420",
|
|
306
|
+
sha384: "3041300d060960864801650304020205000430",
|
|
307
|
+
sha512: "3051300d060960864801650304020305000440",
|
|
308
|
+
};
|
|
309
|
+
function browserCryptoHash(algorithm) {
|
|
310
|
+
return BROWSER_CRYPTO_HASHES[normalizeCryptoHashName(algorithm)];
|
|
311
|
+
}
|
|
312
|
+
function hashDigestBytes(algorithm, data) {
|
|
313
|
+
return browserCryptoHash(algorithm)(toUint8Array(data));
|
|
314
|
+
}
|
|
315
|
+
function hmacDigestBytes(algorithm, key, data) {
|
|
316
|
+
return nobleHmac(browserCryptoHash(algorithm), toUint8Array(key), toUint8Array(data));
|
|
317
|
+
}
|
|
318
|
+
function normalizeSignatureHashName(algorithm) {
|
|
319
|
+
const normalized = String(algorithm)
|
|
320
|
+
.trim()
|
|
321
|
+
.toLowerCase()
|
|
322
|
+
.replace(/^rsa[-_]/, "");
|
|
323
|
+
return normalizeCryptoHashName(normalized);
|
|
324
|
+
}
|
|
325
|
+
function bytesToHex(bytes) {
|
|
326
|
+
return Array.from(bytes, (byte) => byte.toString(16).padStart(2, "0")).join("");
|
|
327
|
+
}
|
|
328
|
+
function hexToBytes(hex) {
|
|
329
|
+
const out = new Uint8Array(hex.length / 2);
|
|
330
|
+
for (let index = 0; index < out.length; index++) {
|
|
331
|
+
out[index] = Number.parseInt(hex.slice(index * 2, index * 2 + 2), 16);
|
|
332
|
+
}
|
|
333
|
+
return out;
|
|
334
|
+
}
|
|
335
|
+
function concatBytes(chunks) {
|
|
336
|
+
const total = chunks.reduce((sum, chunk) => sum + chunk.byteLength, 0);
|
|
337
|
+
const out = new Uint8Array(total);
|
|
338
|
+
let offset = 0;
|
|
339
|
+
for (const chunk of chunks) {
|
|
340
|
+
out.set(chunk, offset);
|
|
341
|
+
offset += chunk.byteLength;
|
|
342
|
+
}
|
|
343
|
+
return out;
|
|
344
|
+
}
|
|
345
|
+
function pemToDerBytes(value) {
|
|
346
|
+
let pem;
|
|
347
|
+
if (typeof value === "string") {
|
|
348
|
+
try {
|
|
349
|
+
const parsed = JSON.parse(value);
|
|
350
|
+
pem = typeof parsed === "string" ? parsed : value;
|
|
351
|
+
}
|
|
352
|
+
catch {
|
|
353
|
+
pem = value;
|
|
354
|
+
}
|
|
355
|
+
}
|
|
356
|
+
else if (value && typeof value === "object" && "key" in value) {
|
|
357
|
+
return pemToDerBytes(value.key);
|
|
358
|
+
}
|
|
359
|
+
else {
|
|
360
|
+
throw new Error("Browser crypto RSA key must be a PEM string");
|
|
361
|
+
}
|
|
362
|
+
const base64 = pem.replace(/-----BEGIN [^-]+-----|-----END [^-]+-----|\s+/g, "");
|
|
363
|
+
return base64ToBytes(base64);
|
|
364
|
+
}
|
|
365
|
+
class DerReader {
|
|
366
|
+
bytes;
|
|
367
|
+
offset = 0;
|
|
368
|
+
constructor(bytes) {
|
|
369
|
+
this.bytes = bytes;
|
|
370
|
+
}
|
|
371
|
+
get position() {
|
|
372
|
+
return this.offset;
|
|
373
|
+
}
|
|
374
|
+
set position(value) {
|
|
375
|
+
this.offset = value;
|
|
376
|
+
}
|
|
377
|
+
readTag(expected) {
|
|
378
|
+
const tag = this.bytes[this.offset++];
|
|
379
|
+
if (tag !== expected) {
|
|
380
|
+
throw new Error(`Invalid RSA key DER tag: expected ${expected}, got ${tag}`);
|
|
381
|
+
}
|
|
382
|
+
const length = this.readLength();
|
|
383
|
+
const end = this.offset + length;
|
|
384
|
+
if (end > this.bytes.byteLength) {
|
|
385
|
+
throw new Error("Invalid RSA key DER length");
|
|
386
|
+
}
|
|
387
|
+
const value = this.bytes.subarray(this.offset, end);
|
|
388
|
+
this.offset = end;
|
|
389
|
+
return value;
|
|
390
|
+
}
|
|
391
|
+
readSequence() {
|
|
392
|
+
return new DerReader(this.readTag(0x30));
|
|
393
|
+
}
|
|
394
|
+
readInteger() {
|
|
395
|
+
let value = this.readTag(0x02);
|
|
396
|
+
while (value.byteLength > 1 && value[0] === 0) {
|
|
397
|
+
value = value.subarray(1);
|
|
398
|
+
}
|
|
399
|
+
return value;
|
|
400
|
+
}
|
|
401
|
+
readOctetString() {
|
|
402
|
+
return this.readTag(0x04);
|
|
403
|
+
}
|
|
404
|
+
readBitString() {
|
|
405
|
+
const value = this.readTag(0x03);
|
|
406
|
+
if (value[0] !== 0) {
|
|
407
|
+
throw new Error("Unsupported RSA key bit string padding");
|
|
408
|
+
}
|
|
409
|
+
return value.subarray(1);
|
|
410
|
+
}
|
|
411
|
+
skipAny() {
|
|
412
|
+
const tag = this.bytes[this.offset++];
|
|
413
|
+
if (tag == null) {
|
|
414
|
+
throw new Error("Invalid RSA key DER");
|
|
415
|
+
}
|
|
416
|
+
const length = this.readLength();
|
|
417
|
+
this.offset += length;
|
|
418
|
+
if (this.offset > this.bytes.byteLength) {
|
|
419
|
+
throw new Error("Invalid RSA key DER length");
|
|
420
|
+
}
|
|
421
|
+
}
|
|
422
|
+
readLength() {
|
|
423
|
+
const first = this.bytes[this.offset++];
|
|
424
|
+
if (first == null) {
|
|
425
|
+
throw new Error("Invalid RSA key DER length");
|
|
426
|
+
}
|
|
427
|
+
if ((first & 0x80) === 0) {
|
|
428
|
+
return first;
|
|
429
|
+
}
|
|
430
|
+
const lengthBytes = first & 0x7f;
|
|
431
|
+
if (lengthBytes === 0 || lengthBytes > 4) {
|
|
432
|
+
throw new Error("Unsupported RSA key DER length");
|
|
433
|
+
}
|
|
434
|
+
let length = 0;
|
|
435
|
+
for (let index = 0; index < lengthBytes; index++) {
|
|
436
|
+
length = (length << 8) | this.bytes[this.offset++];
|
|
437
|
+
}
|
|
438
|
+
return length;
|
|
439
|
+
}
|
|
440
|
+
}
|
|
441
|
+
function bytesToBigInt(bytes) {
|
|
442
|
+
const hex = bytesToHex(bytes);
|
|
443
|
+
return hex.length === 0 ? 0n : BigInt(`0x${hex}`);
|
|
444
|
+
}
|
|
445
|
+
function bigIntToBytes(value, length) {
|
|
446
|
+
let hex = value.toString(16);
|
|
447
|
+
if (hex.length % 2 !== 0) {
|
|
448
|
+
hex = `0${hex}`;
|
|
449
|
+
}
|
|
450
|
+
const bytes = hexToBytes(hex);
|
|
451
|
+
if (bytes.byteLength > length) {
|
|
452
|
+
throw new Error("RSA value exceeds modulus length");
|
|
453
|
+
}
|
|
454
|
+
const out = new Uint8Array(length);
|
|
455
|
+
out.set(bytes, length - bytes.byteLength);
|
|
456
|
+
return out;
|
|
457
|
+
}
|
|
458
|
+
function modPow(base, exponent, modulus) {
|
|
459
|
+
let result = 1n;
|
|
460
|
+
let factor = base % modulus;
|
|
461
|
+
let power = exponent;
|
|
462
|
+
while (power > 0n) {
|
|
463
|
+
if ((power & 1n) === 1n) {
|
|
464
|
+
result = (result * factor) % modulus;
|
|
465
|
+
}
|
|
466
|
+
factor = (factor * factor) % modulus;
|
|
467
|
+
power >>= 1n;
|
|
468
|
+
}
|
|
469
|
+
return result;
|
|
470
|
+
}
|
|
471
|
+
function readRsaPublicKeyFromSequence(reader) {
|
|
472
|
+
const modulusBytes = reader.readInteger();
|
|
473
|
+
const exponentBytes = reader.readInteger();
|
|
474
|
+
return {
|
|
475
|
+
modulus: bytesToBigInt(modulusBytes),
|
|
476
|
+
exponent: bytesToBigInt(exponentBytes),
|
|
477
|
+
modulusLength: modulusBytes.byteLength,
|
|
478
|
+
};
|
|
479
|
+
}
|
|
480
|
+
function parseRsaPublicKey(key) {
|
|
481
|
+
const der = pemToDerBytes(key);
|
|
482
|
+
const outer = new DerReader(der).readSequence();
|
|
483
|
+
outer.skipAny();
|
|
484
|
+
const publicKey = new DerReader(outer.readBitString()).readSequence();
|
|
485
|
+
return readRsaPublicKeyFromSequence(publicKey);
|
|
486
|
+
}
|
|
487
|
+
function parseRsaPrivateKey(key) {
|
|
488
|
+
const der = pemToDerBytes(key);
|
|
489
|
+
const outer = new DerReader(der).readSequence();
|
|
490
|
+
outer.skipAny();
|
|
491
|
+
outer.skipAny();
|
|
492
|
+
const privateKey = new DerReader(outer.readOctetString()).readSequence();
|
|
493
|
+
privateKey.skipAny();
|
|
494
|
+
const modulusBytes = privateKey.readInteger();
|
|
495
|
+
privateKey.skipAny();
|
|
496
|
+
const privateExponentBytes = privateKey.readInteger();
|
|
497
|
+
return {
|
|
498
|
+
modulus: bytesToBigInt(modulusBytes),
|
|
499
|
+
exponent: bytesToBigInt(privateExponentBytes),
|
|
500
|
+
modulusLength: modulusBytes.byteLength,
|
|
501
|
+
};
|
|
502
|
+
}
|
|
503
|
+
function rsaPkcs1DigestInfo(algorithm, data) {
|
|
504
|
+
const hashName = normalizeSignatureHashName(algorithm);
|
|
505
|
+
const digest = hashDigestBytes(hashName, data);
|
|
506
|
+
return {
|
|
507
|
+
hashName,
|
|
508
|
+
encoded: concatBytes([hexToBytes(RSA_PKCS1_DIGEST_PREFIXES[hashName]), digest]),
|
|
509
|
+
};
|
|
510
|
+
}
|
|
511
|
+
function rsaPkcs1EncodeDigestInfo(hashName, digestInfo, length) {
|
|
512
|
+
const minimumLength = digestInfo.byteLength + 11;
|
|
513
|
+
if (length < minimumLength) {
|
|
514
|
+
throw new Error(`RSA key is too small for ${hashName} signature`);
|
|
515
|
+
}
|
|
516
|
+
const out = new Uint8Array(length);
|
|
517
|
+
out[0] = 0;
|
|
518
|
+
out[1] = 1;
|
|
519
|
+
out.fill(0xff, 2, length - digestInfo.byteLength - 1);
|
|
520
|
+
out[length - digestInfo.byteLength - 1] = 0;
|
|
521
|
+
out.set(digestInfo, length - digestInfo.byteLength);
|
|
522
|
+
return out;
|
|
523
|
+
}
|
|
524
|
+
function browserRsaSign(algorithm, data, key) {
|
|
525
|
+
const privateKey = parseRsaPrivateKey(key);
|
|
526
|
+
const { hashName, encoded } = rsaPkcs1DigestInfo(algorithm, data);
|
|
527
|
+
const padded = rsaPkcs1EncodeDigestInfo(hashName, encoded, privateKey.modulusLength);
|
|
528
|
+
const signature = modPow(bytesToBigInt(padded), privateKey.exponent, privateKey.modulus);
|
|
529
|
+
return bigIntToBytes(signature, privateKey.modulusLength);
|
|
530
|
+
}
|
|
531
|
+
function browserRsaVerify(algorithm, data, key, signatureValue) {
|
|
532
|
+
const publicKey = parseRsaPublicKey(key);
|
|
533
|
+
const signature = toUint8Array(signatureValue);
|
|
534
|
+
if (signature.byteLength !== publicKey.modulusLength) {
|
|
535
|
+
return false;
|
|
536
|
+
}
|
|
537
|
+
const { hashName, encoded } = rsaPkcs1DigestInfo(algorithm, data);
|
|
538
|
+
const expected = rsaPkcs1EncodeDigestInfo(hashName, encoded, publicKey.modulusLength);
|
|
539
|
+
const actual = bigIntToBytes(modPow(bytesToBigInt(signature), publicKey.exponent, publicKey.modulus), publicKey.modulusLength);
|
|
540
|
+
if (actual.byteLength !== expected.byteLength) {
|
|
541
|
+
return false;
|
|
542
|
+
}
|
|
543
|
+
let diff = 0;
|
|
544
|
+
for (let index = 0; index < expected.byteLength; index++) {
|
|
545
|
+
diff |= actual[index] ^ expected[index];
|
|
546
|
+
}
|
|
547
|
+
return diff === 0;
|
|
548
|
+
}
|
|
549
|
+
const BROWSER_RSA_PKCS1_PADDING = 1;
|
|
550
|
+
const BROWSER_RSA_PKCS1_OAEP_PADDING = 4;
|
|
551
|
+
function normalizeRsaAsymmetricOptions(value) {
|
|
552
|
+
if (value == null)
|
|
553
|
+
return {};
|
|
554
|
+
if (typeof value === "string") {
|
|
555
|
+
const parsed = JSON.parse(value);
|
|
556
|
+
return parsed && typeof parsed === "object"
|
|
557
|
+
? parsed
|
|
558
|
+
: {};
|
|
559
|
+
}
|
|
560
|
+
return typeof value === "object" ? value : {};
|
|
561
|
+
}
|
|
562
|
+
function randomNonZeroBytes(length) {
|
|
563
|
+
const bytes = new Uint8Array(length);
|
|
564
|
+
const crypto = globalThis.crypto;
|
|
565
|
+
if (!crypto?.getRandomValues) {
|
|
566
|
+
throw new Error("Browser runtime crypto requires getRandomValues support");
|
|
567
|
+
}
|
|
568
|
+
let offset = 0;
|
|
569
|
+
while (offset < length) {
|
|
570
|
+
const candidate = new Uint8Array(length - offset);
|
|
571
|
+
crypto.getRandomValues(candidate);
|
|
572
|
+
for (const byte of candidate) {
|
|
573
|
+
if (byte !== 0) {
|
|
574
|
+
bytes[offset++] = byte;
|
|
575
|
+
if (offset === length)
|
|
576
|
+
break;
|
|
577
|
+
}
|
|
578
|
+
}
|
|
579
|
+
}
|
|
580
|
+
return bytes;
|
|
581
|
+
}
|
|
582
|
+
function xorBytes(left, right) {
|
|
583
|
+
if (left.byteLength !== right.byteLength) {
|
|
584
|
+
throw new Error("RSA OAEP mask length mismatch");
|
|
585
|
+
}
|
|
586
|
+
const out = new Uint8Array(left.byteLength);
|
|
587
|
+
for (let index = 0; index < out.byteLength; index++) {
|
|
588
|
+
out[index] = left[index] ^ right[index];
|
|
589
|
+
}
|
|
590
|
+
return out;
|
|
591
|
+
}
|
|
592
|
+
function mgf1(seed, length, hashName) {
|
|
593
|
+
const chunks = [];
|
|
594
|
+
let counter = 0;
|
|
595
|
+
let remaining = length;
|
|
596
|
+
while (remaining > 0) {
|
|
597
|
+
const counterBytes = new Uint8Array([
|
|
598
|
+
(counter >>> 24) & 0xff,
|
|
599
|
+
(counter >>> 16) & 0xff,
|
|
600
|
+
(counter >>> 8) & 0xff,
|
|
601
|
+
counter & 0xff,
|
|
602
|
+
]);
|
|
603
|
+
const digest = hashDigestBytes(hashName, concatBytes([seed, counterBytes]));
|
|
604
|
+
chunks.push(digest.subarray(0, Math.min(remaining, digest.byteLength)));
|
|
605
|
+
remaining -= digest.byteLength;
|
|
606
|
+
counter++;
|
|
607
|
+
}
|
|
608
|
+
return concatBytes(chunks).subarray(0, length);
|
|
609
|
+
}
|
|
610
|
+
function normalizeOaepHashName(value) {
|
|
611
|
+
return value == null ? "sha1" : normalizeCryptoHashName(value);
|
|
612
|
+
}
|
|
613
|
+
function rsaEncryptPkcs1(key, data) {
|
|
614
|
+
const paddingLength = key.modulusLength - data.byteLength - 3;
|
|
615
|
+
if (paddingLength < 8) {
|
|
616
|
+
throw new Error("RSA_PKCS1_PADDING input is too large for key size");
|
|
617
|
+
}
|
|
618
|
+
const encoded = concatBytes([
|
|
619
|
+
new Uint8Array([0, 2]),
|
|
620
|
+
randomNonZeroBytes(paddingLength),
|
|
621
|
+
new Uint8Array([0]),
|
|
622
|
+
data,
|
|
623
|
+
]);
|
|
624
|
+
const encrypted = modPow(bytesToBigInt(encoded), key.exponent, key.modulus);
|
|
625
|
+
return bigIntToBytes(encrypted, key.modulusLength);
|
|
626
|
+
}
|
|
627
|
+
function rsaDecryptPkcs1(key, encrypted) {
|
|
628
|
+
if (encrypted.byteLength !== key.modulusLength) {
|
|
629
|
+
throw new Error("RSA_PKCS1_PADDING encrypted input has invalid length");
|
|
630
|
+
}
|
|
631
|
+
const encoded = bigIntToBytes(modPow(bytesToBigInt(encrypted), key.exponent, key.modulus), key.modulusLength);
|
|
632
|
+
if (encoded[0] !== 0 || encoded[1] !== 2) {
|
|
633
|
+
throw new Error("RSA_PKCS1_PADDING block is invalid");
|
|
634
|
+
}
|
|
635
|
+
let separator = -1;
|
|
636
|
+
for (let index = 2; index < encoded.byteLength; index++) {
|
|
637
|
+
if (encoded[index] === 0) {
|
|
638
|
+
separator = index;
|
|
639
|
+
break;
|
|
640
|
+
}
|
|
641
|
+
}
|
|
642
|
+
if (separator < 10) {
|
|
643
|
+
throw new Error("RSA_PKCS1_PADDING block is invalid");
|
|
644
|
+
}
|
|
645
|
+
return encoded.subarray(separator + 1);
|
|
646
|
+
}
|
|
647
|
+
function rsaEncryptOaep(key, data, options) {
|
|
648
|
+
const hashName = normalizeOaepHashName(options.oaepHash);
|
|
649
|
+
const label = optionalBytes(options.oaepLabel);
|
|
650
|
+
const hash = browserCryptoHash(hashName);
|
|
651
|
+
const hLen = hash.outputLen;
|
|
652
|
+
const maxMessageLength = key.modulusLength - 2 * hLen - 2;
|
|
653
|
+
if (data.byteLength > maxMessageLength) {
|
|
654
|
+
throw new Error("RSA_PKCS1_OAEP_PADDING input is too large for key size");
|
|
655
|
+
}
|
|
656
|
+
const lHash = hash(label);
|
|
657
|
+
const ps = new Uint8Array(maxMessageLength - data.byteLength);
|
|
658
|
+
const db = concatBytes([lHash, ps, new Uint8Array([1]), data]);
|
|
659
|
+
const seed = new Uint8Array(hLen);
|
|
660
|
+
if (!globalThis.crypto?.getRandomValues) {
|
|
661
|
+
throw new Error("Browser runtime crypto requires getRandomValues support");
|
|
662
|
+
}
|
|
663
|
+
globalThis.crypto.getRandomValues(seed);
|
|
664
|
+
const dbMask = mgf1(seed, key.modulusLength - hLen - 1, hashName);
|
|
665
|
+
const maskedDb = xorBytes(db, dbMask);
|
|
666
|
+
const seedMask = mgf1(maskedDb, hLen, hashName);
|
|
667
|
+
const maskedSeed = xorBytes(seed, seedMask);
|
|
668
|
+
const encoded = concatBytes([new Uint8Array([0]), maskedSeed, maskedDb]);
|
|
669
|
+
const encrypted = modPow(bytesToBigInt(encoded), key.exponent, key.modulus);
|
|
670
|
+
return bigIntToBytes(encrypted, key.modulusLength);
|
|
671
|
+
}
|
|
672
|
+
function rsaDecryptOaep(key, encrypted, options) {
|
|
673
|
+
if (encrypted.byteLength !== key.modulusLength) {
|
|
674
|
+
throw new Error("RSA_PKCS1_OAEP_PADDING encrypted input has invalid length");
|
|
675
|
+
}
|
|
676
|
+
const hashName = normalizeOaepHashName(options.oaepHash);
|
|
677
|
+
const label = optionalBytes(options.oaepLabel);
|
|
678
|
+
const hash = browserCryptoHash(hashName);
|
|
679
|
+
const hLen = hash.outputLen;
|
|
680
|
+
if (key.modulusLength < 2 * hLen + 2) {
|
|
681
|
+
throw new Error("RSA key is too small for OAEP");
|
|
682
|
+
}
|
|
683
|
+
const encoded = bigIntToBytes(modPow(bytesToBigInt(encrypted), key.exponent, key.modulus), key.modulusLength);
|
|
684
|
+
const maskedSeed = encoded.subarray(1, 1 + hLen);
|
|
685
|
+
const maskedDb = encoded.subarray(1 + hLen);
|
|
686
|
+
const seedMask = mgf1(maskedDb, hLen, hashName);
|
|
687
|
+
const seed = xorBytes(maskedSeed, seedMask);
|
|
688
|
+
const dbMask = mgf1(seed, key.modulusLength - hLen - 1, hashName);
|
|
689
|
+
const db = xorBytes(maskedDb, dbMask);
|
|
690
|
+
const lHash = hash(label);
|
|
691
|
+
let diff = encoded[0];
|
|
692
|
+
for (let index = 0; index < hLen; index++) {
|
|
693
|
+
diff |= db[index] ^ lHash[index];
|
|
694
|
+
}
|
|
695
|
+
let separator = -1;
|
|
696
|
+
for (let index = hLen; index < db.byteLength; index++) {
|
|
697
|
+
if (separator === -1 && db[index] === 1) {
|
|
698
|
+
separator = index;
|
|
699
|
+
break;
|
|
700
|
+
}
|
|
701
|
+
if (db[index] !== 0) {
|
|
702
|
+
diff |= db[index];
|
|
703
|
+
}
|
|
704
|
+
}
|
|
705
|
+
if (diff !== 0 || separator === -1) {
|
|
706
|
+
throw new Error("RSA_PKCS1_OAEP_PADDING block is invalid");
|
|
707
|
+
}
|
|
708
|
+
return db.subarray(separator + 1);
|
|
709
|
+
}
|
|
710
|
+
function browserRsaAsymmetricOp(operation, keyValue, dataValue, optionsValue) {
|
|
711
|
+
const operationName = String(operation);
|
|
712
|
+
const options = normalizeRsaAsymmetricOptions(optionsValue);
|
|
713
|
+
const padding = options.padding == null
|
|
714
|
+
? BROWSER_RSA_PKCS1_OAEP_PADDING
|
|
715
|
+
: Number(options.padding);
|
|
716
|
+
const data = toUint8Array(dataValue);
|
|
717
|
+
if (operationName === "publicEncrypt") {
|
|
718
|
+
const publicKey = parseRsaPublicKey(keyValue);
|
|
719
|
+
if (padding === BROWSER_RSA_PKCS1_PADDING) {
|
|
720
|
+
return rsaEncryptPkcs1(publicKey, data);
|
|
721
|
+
}
|
|
722
|
+
if (padding === BROWSER_RSA_PKCS1_OAEP_PADDING) {
|
|
723
|
+
return rsaEncryptOaep(publicKey, data, options);
|
|
724
|
+
}
|
|
725
|
+
}
|
|
726
|
+
if (operationName === "privateDecrypt") {
|
|
727
|
+
const privateKey = parseRsaPrivateKey(keyValue);
|
|
728
|
+
if (padding === BROWSER_RSA_PKCS1_PADDING) {
|
|
729
|
+
return rsaDecryptPkcs1(privateKey, data);
|
|
730
|
+
}
|
|
731
|
+
if (padding === BROWSER_RSA_PKCS1_OAEP_PADDING) {
|
|
732
|
+
return rsaDecryptOaep(privateKey, data, options);
|
|
733
|
+
}
|
|
734
|
+
}
|
|
735
|
+
throw new Error(`Unsupported browser RSA asymmetric operation: ${operationName}`);
|
|
736
|
+
}
|
|
737
|
+
function pbkdf2Bytes(password, salt, iterations, keyLength, algorithm) {
|
|
738
|
+
const normalizedIterations = Number(iterations);
|
|
739
|
+
if (!Number.isInteger(normalizedIterations) || normalizedIterations <= 0) {
|
|
740
|
+
throw new Error("crypto.pbkdf2 iterations must be greater than zero");
|
|
741
|
+
}
|
|
742
|
+
const length = Number(keyLength);
|
|
743
|
+
if (!Number.isInteger(length) || length < 0) {
|
|
744
|
+
throw new Error("crypto.pbkdf2 key length must be a non-negative integer");
|
|
745
|
+
}
|
|
746
|
+
return noblePbkdf2(browserCryptoHash(algorithm), toUint8Array(password), toUint8Array(salt), { c: normalizedIterations, dkLen: length });
|
|
747
|
+
}
|
|
748
|
+
function normalizeBrowserAesAlgorithm(algorithm) {
|
|
749
|
+
const normalized = String(algorithm).toLowerCase();
|
|
750
|
+
const match = /^aes-(128|192|256)-(cbc|ctr|gcm)$/.exec(normalized);
|
|
751
|
+
if (!match) {
|
|
752
|
+
throw new Error(`Unsupported browser crypto cipher: ${algorithm}`);
|
|
753
|
+
}
|
|
754
|
+
return {
|
|
755
|
+
keyLength: Number(match[1]) / 8,
|
|
756
|
+
mode: match[2],
|
|
757
|
+
};
|
|
758
|
+
}
|
|
759
|
+
function normalizeBrowserCipherivOptions(optionsJson) {
|
|
760
|
+
if (optionsJson == null)
|
|
761
|
+
return {};
|
|
762
|
+
if (typeof optionsJson === "string") {
|
|
763
|
+
const parsed = JSON.parse(optionsJson);
|
|
764
|
+
return parsed && typeof parsed === "object"
|
|
765
|
+
? parsed
|
|
766
|
+
: {};
|
|
767
|
+
}
|
|
768
|
+
return typeof optionsJson === "object"
|
|
769
|
+
? optionsJson
|
|
770
|
+
: {};
|
|
771
|
+
}
|
|
772
|
+
function optionalBytes(value) {
|
|
773
|
+
return value == null ? new Uint8Array(0) : toUint8Array(value);
|
|
774
|
+
}
|
|
775
|
+
function assertAesInputLengths(algorithm, key, iv, mode, keyLength) {
|
|
776
|
+
if (key.byteLength !== keyLength) {
|
|
777
|
+
throw new Error(`Invalid key length for ${String(algorithm)}: expected ${keyLength} bytes, got ${key.byteLength}`);
|
|
778
|
+
}
|
|
779
|
+
if ((mode === "cbc" || mode === "ctr") && iv.byteLength !== 16) {
|
|
780
|
+
throw new Error(`Invalid IV length for ${String(algorithm)}: expected 16 bytes, got ${iv.byteLength}`);
|
|
781
|
+
}
|
|
782
|
+
if (mode === "gcm" && iv.byteLength < 8) {
|
|
783
|
+
throw new Error(`Invalid IV length for ${String(algorithm)}: expected at least 8 bytes, got ${iv.byteLength}`);
|
|
784
|
+
}
|
|
785
|
+
}
|
|
786
|
+
function browserCipheriv(algorithm, keyValue, ivValue, dataValue, optionsJson) {
|
|
787
|
+
const { mode, keyLength } = normalizeBrowserAesAlgorithm(algorithm);
|
|
788
|
+
const key = toUint8Array(keyValue);
|
|
789
|
+
const iv = toUint8Array(ivValue);
|
|
790
|
+
const data = toUint8Array(dataValue);
|
|
791
|
+
const options = normalizeBrowserCipherivOptions(optionsJson);
|
|
792
|
+
assertAesInputLengths(algorithm, key, iv, mode, keyLength);
|
|
793
|
+
if (mode === "cbc") {
|
|
794
|
+
return nobleAesCbc(key, iv, {
|
|
795
|
+
disablePadding: options.autoPadding === false,
|
|
796
|
+
}).encrypt(data);
|
|
797
|
+
}
|
|
798
|
+
if (mode === "ctr") {
|
|
799
|
+
return nobleAesCtr(key, iv).encrypt(data);
|
|
800
|
+
}
|
|
801
|
+
const encrypted = nobleAesGcm(key, iv, optionalBytes(options.aad)).encrypt(data);
|
|
802
|
+
const tagLength = nobleAesGcm.tagLength;
|
|
803
|
+
const ciphertextLength = encrypted.byteLength - tagLength;
|
|
804
|
+
const out = new Uint8Array(encrypted.byteLength);
|
|
805
|
+
out.set(encrypted.subarray(0, ciphertextLength), 0);
|
|
806
|
+
out.set(encrypted.subarray(ciphertextLength), ciphertextLength);
|
|
807
|
+
return out;
|
|
808
|
+
}
|
|
809
|
+
function browserDecipheriv(algorithm, keyValue, ivValue, dataValue, optionsJson) {
|
|
810
|
+
const { mode, keyLength } = normalizeBrowserAesAlgorithm(algorithm);
|
|
811
|
+
const key = toUint8Array(keyValue);
|
|
812
|
+
const iv = toUint8Array(ivValue);
|
|
813
|
+
const data = toUint8Array(dataValue);
|
|
814
|
+
const options = normalizeBrowserCipherivOptions(optionsJson);
|
|
815
|
+
assertAesInputLengths(algorithm, key, iv, mode, keyLength);
|
|
816
|
+
if (mode === "cbc") {
|
|
817
|
+
return nobleAesCbc(key, iv, {
|
|
818
|
+
disablePadding: options.autoPadding === false,
|
|
819
|
+
}).decrypt(data);
|
|
820
|
+
}
|
|
821
|
+
if (mode === "ctr") {
|
|
822
|
+
return nobleAesCtr(key, iv).decrypt(data);
|
|
823
|
+
}
|
|
824
|
+
const authTag = optionalBytes(options.authTag);
|
|
825
|
+
if (authTag.byteLength === 0) {
|
|
826
|
+
throw new Error(`Missing auth tag for ${String(algorithm)} decipher`);
|
|
827
|
+
}
|
|
828
|
+
const combined = new Uint8Array(data.byteLength + authTag.byteLength);
|
|
829
|
+
combined.set(data, 0);
|
|
830
|
+
combined.set(authTag, data.byteLength);
|
|
831
|
+
return nobleAesGcm(key, iv, optionalBytes(options.aad)).decrypt(combined);
|
|
832
|
+
}
|
|
833
|
+
function unsupportedBrowserCrypto(operation) {
|
|
834
|
+
const error = new Error(`ERR_UNSUPPORTED_BROWSER_CRYPTO: node:crypto ${operation} is not implemented in the browser runtime yet`);
|
|
835
|
+
error.code = "ERR_UNSUPPORTED_BROWSER_CRYPTO";
|
|
836
|
+
throw error;
|
|
837
|
+
}
|
|
838
|
+
function boundErrorMessage(message) {
|
|
839
|
+
if (message.length <= MAX_ERROR_MESSAGE_CHARS) {
|
|
840
|
+
return message;
|
|
841
|
+
}
|
|
842
|
+
return `${message.slice(0, MAX_ERROR_MESSAGE_CHARS)}...[Truncated]`;
|
|
843
|
+
}
|
|
844
|
+
function boundStdioMessage(message) {
|
|
845
|
+
if (message.length <= MAX_STDIO_MESSAGE_CHARS) {
|
|
846
|
+
return message;
|
|
847
|
+
}
|
|
848
|
+
return `${message.slice(0, MAX_STDIO_MESSAGE_CHARS)}...[Truncated]`;
|
|
849
|
+
}
|
|
850
|
+
function revivePermission(source) {
|
|
851
|
+
if (!source)
|
|
852
|
+
return undefined;
|
|
853
|
+
// Validate source before eval to prevent code injection
|
|
854
|
+
if (!validatePermissionSource(source))
|
|
855
|
+
return undefined;
|
|
856
|
+
try {
|
|
857
|
+
const fn = new Function(`return (${source});`)();
|
|
858
|
+
if (typeof fn === "function")
|
|
859
|
+
return fn;
|
|
860
|
+
return undefined;
|
|
861
|
+
}
|
|
862
|
+
catch {
|
|
863
|
+
return undefined;
|
|
864
|
+
}
|
|
865
|
+
}
|
|
866
|
+
/** Deserialize permission callbacks that were stringified for transfer across the Worker boundary. */
|
|
867
|
+
function revivePermissions(serialized) {
|
|
868
|
+
if (!serialized)
|
|
869
|
+
return undefined;
|
|
870
|
+
const perms = {};
|
|
871
|
+
perms.fs = revivePermission(serialized.fs);
|
|
872
|
+
perms.network = revivePermission(serialized.network);
|
|
873
|
+
perms.childProcess = revivePermission(serialized.childProcess);
|
|
874
|
+
perms.env = revivePermission(serialized.env);
|
|
875
|
+
return perms;
|
|
876
|
+
}
|
|
877
|
+
/**
|
|
878
|
+
* Wrap a sync function in the bridge calling convention (`applySync`) so
|
|
879
|
+
* bridge code can call it the same way it calls bridge References.
|
|
880
|
+
*/
|
|
881
|
+
function makeApplySync(fn) {
|
|
882
|
+
const applySync = (_ctx, args) => fn(...args);
|
|
883
|
+
return {
|
|
884
|
+
applySync,
|
|
885
|
+
applySyncPromise: applySync,
|
|
886
|
+
};
|
|
887
|
+
}
|
|
888
|
+
function makeApplySyncPromise(fn) {
|
|
889
|
+
return {
|
|
890
|
+
applySyncPromise(_ctx, args) {
|
|
891
|
+
return fn(...args);
|
|
892
|
+
},
|
|
893
|
+
};
|
|
894
|
+
}
|
|
895
|
+
function makeApplyPromise(fn) {
|
|
896
|
+
return {
|
|
897
|
+
apply(_ctx, args) {
|
|
898
|
+
return fn(...args);
|
|
899
|
+
},
|
|
900
|
+
};
|
|
901
|
+
}
|
|
902
|
+
function normalizeTextEncoding(options) {
|
|
903
|
+
if (typeof options === "string") {
|
|
904
|
+
return options;
|
|
905
|
+
}
|
|
906
|
+
if (options && typeof options === "object" && "encoding" in options) {
|
|
907
|
+
const encoding = options.encoding;
|
|
908
|
+
return typeof encoding === "string" ? encoding : null;
|
|
909
|
+
}
|
|
910
|
+
return null;
|
|
911
|
+
}
|
|
912
|
+
function toNodeBuffer(bytes) {
|
|
913
|
+
if (typeof Buffer === "function") {
|
|
914
|
+
return Buffer.from(bytes);
|
|
915
|
+
}
|
|
916
|
+
return bytes;
|
|
917
|
+
}
|
|
918
|
+
function createStats(stat) {
|
|
919
|
+
return {
|
|
920
|
+
...stat,
|
|
921
|
+
isFile: () => !stat.isDirectory && !stat.isSymbolicLink,
|
|
922
|
+
isDirectory: () => stat.isDirectory,
|
|
923
|
+
isSymbolicLink: () => stat.isSymbolicLink,
|
|
924
|
+
};
|
|
925
|
+
}
|
|
926
|
+
function createDirent(entry) {
|
|
927
|
+
return {
|
|
928
|
+
name: entry.name,
|
|
929
|
+
isFile: () => !entry.isDirectory && !entry.isSymbolicLink,
|
|
930
|
+
isDirectory: () => entry.isDirectory,
|
|
931
|
+
isSymbolicLink: () => Boolean(entry.isSymbolicLink),
|
|
932
|
+
};
|
|
933
|
+
}
|
|
934
|
+
function createFsModule(syncBridge) {
|
|
935
|
+
const readFileSync = (path, options) => {
|
|
936
|
+
const encoding = normalizeTextEncoding(options);
|
|
937
|
+
if (encoding) {
|
|
938
|
+
return syncBridge.requestText("fs.readFile", [path]);
|
|
939
|
+
}
|
|
940
|
+
return toNodeBuffer(syncBridge.requestBinary("fs.readFileBinary", [path]));
|
|
941
|
+
};
|
|
942
|
+
const writeFileSync = (path, content) => {
|
|
943
|
+
if (typeof content === "string") {
|
|
944
|
+
syncBridge.requestVoid("fs.writeFile", [path, content]);
|
|
945
|
+
return;
|
|
946
|
+
}
|
|
947
|
+
syncBridge.requestVoid("fs.writeFileBinary", [path, toUint8Array(content)]);
|
|
948
|
+
};
|
|
949
|
+
const mkdirSync = (path, options) => {
|
|
950
|
+
const recursive = typeof options === "boolean" ? options : (options?.recursive ?? true);
|
|
951
|
+
if (recursive) {
|
|
952
|
+
syncBridge.requestVoid("fs.mkdir", [path]);
|
|
953
|
+
return;
|
|
954
|
+
}
|
|
955
|
+
syncBridge.requestVoid("fs.createDir", [path]);
|
|
956
|
+
};
|
|
957
|
+
const readdirSync = (path, options) => {
|
|
958
|
+
const entries = syncBridge.requestJson("fs.readDir", [
|
|
959
|
+
path,
|
|
960
|
+
]);
|
|
961
|
+
if (options?.withFileTypes) {
|
|
962
|
+
return entries.map((entry) => createDirent(entry));
|
|
963
|
+
}
|
|
964
|
+
return entries.map((entry) => entry.name);
|
|
965
|
+
};
|
|
966
|
+
const statSync = (path) => createStats(syncBridge.requestJson("fs.stat", [path]));
|
|
967
|
+
const lstatSync = (path) => createStats(syncBridge.requestJson("fs.lstat", [path]));
|
|
968
|
+
const promises = {
|
|
969
|
+
readFile(path, options) {
|
|
970
|
+
return Promise.resolve(readFileSync(path, options));
|
|
971
|
+
},
|
|
972
|
+
writeFile(path, content) {
|
|
973
|
+
writeFileSync(path, content);
|
|
974
|
+
return Promise.resolve();
|
|
975
|
+
},
|
|
976
|
+
mkdir(path, options) {
|
|
977
|
+
mkdirSync(path, options);
|
|
978
|
+
return Promise.resolve();
|
|
979
|
+
},
|
|
980
|
+
readdir(path, options) {
|
|
981
|
+
return Promise.resolve(readdirSync(path, options));
|
|
982
|
+
},
|
|
983
|
+
stat(path) {
|
|
984
|
+
return Promise.resolve(statSync(path));
|
|
985
|
+
},
|
|
986
|
+
lstat(path) {
|
|
987
|
+
return Promise.resolve(lstatSync(path));
|
|
988
|
+
},
|
|
989
|
+
unlink(path) {
|
|
990
|
+
syncBridge.requestVoid("fs.unlink", [path]);
|
|
991
|
+
return Promise.resolve();
|
|
992
|
+
},
|
|
993
|
+
rmdir(path) {
|
|
994
|
+
syncBridge.requestVoid("fs.rmdir", [path]);
|
|
995
|
+
return Promise.resolve();
|
|
996
|
+
},
|
|
997
|
+
rm(path) {
|
|
998
|
+
syncBridge.requestVoid("fs.unlink", [path]);
|
|
999
|
+
return Promise.resolve();
|
|
1000
|
+
},
|
|
1001
|
+
rename(oldPath, newPath) {
|
|
1002
|
+
syncBridge.requestVoid("fs.rename", [oldPath, newPath]);
|
|
1003
|
+
return Promise.resolve();
|
|
1004
|
+
},
|
|
1005
|
+
realpath(path) {
|
|
1006
|
+
return Promise.resolve(syncBridge.requestText("fs.realpath", [path]));
|
|
1007
|
+
},
|
|
1008
|
+
readlink(path) {
|
|
1009
|
+
return Promise.resolve(syncBridge.requestText("fs.readlink", [path]));
|
|
1010
|
+
},
|
|
1011
|
+
symlink(target, path) {
|
|
1012
|
+
syncBridge.requestVoid("fs.symlink", [target, path]);
|
|
1013
|
+
return Promise.resolve();
|
|
1014
|
+
},
|
|
1015
|
+
link(existingPath, newPath) {
|
|
1016
|
+
syncBridge.requestVoid("fs.link", [existingPath, newPath]);
|
|
1017
|
+
return Promise.resolve();
|
|
1018
|
+
},
|
|
1019
|
+
chmod(path, mode) {
|
|
1020
|
+
syncBridge.requestVoid("fs.chmod", [path, mode]);
|
|
1021
|
+
return Promise.resolve();
|
|
1022
|
+
},
|
|
1023
|
+
truncate(path, length = 0) {
|
|
1024
|
+
syncBridge.requestVoid("fs.truncate", [path, length]);
|
|
1025
|
+
return Promise.resolve();
|
|
1026
|
+
},
|
|
1027
|
+
};
|
|
1028
|
+
return {
|
|
1029
|
+
readFileSync,
|
|
1030
|
+
writeFileSync,
|
|
1031
|
+
mkdirSync,
|
|
1032
|
+
readdirSync,
|
|
1033
|
+
existsSync(path) {
|
|
1034
|
+
return syncBridge.requestJson("fs.exists", [path]);
|
|
1035
|
+
},
|
|
1036
|
+
statSync,
|
|
1037
|
+
lstatSync,
|
|
1038
|
+
unlinkSync(path) {
|
|
1039
|
+
syncBridge.requestVoid("fs.unlink", [path]);
|
|
1040
|
+
},
|
|
1041
|
+
rmdirSync(path) {
|
|
1042
|
+
syncBridge.requestVoid("fs.rmdir", [path]);
|
|
1043
|
+
},
|
|
1044
|
+
rmSync(path) {
|
|
1045
|
+
syncBridge.requestVoid("fs.unlink", [path]);
|
|
1046
|
+
},
|
|
1047
|
+
renameSync(oldPath, newPath) {
|
|
1048
|
+
syncBridge.requestVoid("fs.rename", [oldPath, newPath]);
|
|
1049
|
+
},
|
|
1050
|
+
realpathSync(path) {
|
|
1051
|
+
return syncBridge.requestText("fs.realpath", [path]);
|
|
1052
|
+
},
|
|
1053
|
+
readlinkSync(path) {
|
|
1054
|
+
return syncBridge.requestText("fs.readlink", [path]);
|
|
1055
|
+
},
|
|
1056
|
+
symlinkSync(target, path) {
|
|
1057
|
+
syncBridge.requestVoid("fs.symlink", [target, path]);
|
|
1058
|
+
},
|
|
1059
|
+
linkSync(existingPath, newPath) {
|
|
1060
|
+
syncBridge.requestVoid("fs.link", [existingPath, newPath]);
|
|
1061
|
+
},
|
|
1062
|
+
chmodSync(path, mode) {
|
|
1063
|
+
syncBridge.requestVoid("fs.chmod", [path, mode]);
|
|
1064
|
+
},
|
|
1065
|
+
truncateSync(path, length = 0) {
|
|
1066
|
+
syncBridge.requestVoid("fs.truncate", [path, length]);
|
|
1067
|
+
},
|
|
1068
|
+
promises,
|
|
1069
|
+
};
|
|
1070
|
+
}
|
|
1071
|
+
// Save real postMessage before sandbox code can replace it
|
|
1072
|
+
const _realPostMessage = self.postMessage.bind(self);
|
|
1073
|
+
function postResponse(message) {
|
|
1074
|
+
_realPostMessage({
|
|
1075
|
+
controlToken: getRequiredControlToken(),
|
|
1076
|
+
...message,
|
|
1077
|
+
});
|
|
1078
|
+
}
|
|
1079
|
+
function postAsyncResponse(id, promise) {
|
|
1080
|
+
void promise.then((result) => {
|
|
1081
|
+
postResponse({ type: "response", id, ok: true, result });
|
|
1082
|
+
}, (err) => {
|
|
1083
|
+
const error = err;
|
|
1084
|
+
postResponse({
|
|
1085
|
+
type: "response",
|
|
1086
|
+
id,
|
|
1087
|
+
ok: false,
|
|
1088
|
+
error: {
|
|
1089
|
+
message: error?.message ?? String(err),
|
|
1090
|
+
stack: error?.stack,
|
|
1091
|
+
code: error?.code,
|
|
1092
|
+
},
|
|
1093
|
+
});
|
|
1094
|
+
});
|
|
1095
|
+
}
|
|
1096
|
+
function postSyncRequest(message) {
|
|
1097
|
+
_realPostMessage({
|
|
1098
|
+
controlToken: getRequiredControlToken(),
|
|
1099
|
+
...message,
|
|
1100
|
+
});
|
|
1101
|
+
}
|
|
1102
|
+
function postStdio(executionId, requestId, channel, message) {
|
|
1103
|
+
const payload = {
|
|
1104
|
+
controlToken: getRequiredControlToken(),
|
|
1105
|
+
type: "stdio",
|
|
1106
|
+
executionId,
|
|
1107
|
+
requestId,
|
|
1108
|
+
channel,
|
|
1109
|
+
message,
|
|
1110
|
+
};
|
|
1111
|
+
_realPostMessage(payload);
|
|
1112
|
+
}
|
|
1113
|
+
function emitStdio(executionId, requestId, channel, message) {
|
|
1114
|
+
postStdio(executionId, requestId, channel, boundStdioMessage(message));
|
|
1115
|
+
}
|
|
1116
|
+
function emitActiveStdio(channel, args) {
|
|
1117
|
+
if (!activeCaptureStdio ||
|
|
1118
|
+
activeProcessRequestId === null ||
|
|
1119
|
+
activeExecutionId === null) {
|
|
1120
|
+
return;
|
|
1121
|
+
}
|
|
1122
|
+
const message = args.map((arg) => normalizeProcessOutputChunk(arg)).join(" ");
|
|
1123
|
+
emitStdio(activeExecutionId, activeProcessRequestId, channel, message);
|
|
1124
|
+
}
|
|
1125
|
+
function createSyncBridgeClient(payload) {
|
|
1126
|
+
const signal = new Int32Array(payload.signalBuffer);
|
|
1127
|
+
const data = new Uint8Array(payload.dataBuffer);
|
|
1128
|
+
let nextRequestId = 1;
|
|
1129
|
+
const timeoutMs = payload.timeoutMs ?? 30_000;
|
|
1130
|
+
function readBytes(length) {
|
|
1131
|
+
if (length <= 0) {
|
|
1132
|
+
return new Uint8Array(0);
|
|
1133
|
+
}
|
|
1134
|
+
return data.slice(0, length);
|
|
1135
|
+
}
|
|
1136
|
+
function requestRaw(operation, args) {
|
|
1137
|
+
if (!activeExecutionId || activeProcessRequestId === null) {
|
|
1138
|
+
throw new Error(`Browser runtime sync bridge ${operation} called outside an active execution`);
|
|
1139
|
+
}
|
|
1140
|
+
Atomics.store(signal, SYNC_BRIDGE_SIGNAL_STATE_INDEX, SYNC_BRIDGE_SIGNAL_STATE_IDLE);
|
|
1141
|
+
Atomics.store(signal, SYNC_BRIDGE_SIGNAL_STATUS_INDEX, 0);
|
|
1142
|
+
Atomics.store(signal, SYNC_BRIDGE_SIGNAL_KIND_INDEX, SYNC_BRIDGE_KIND_NONE);
|
|
1143
|
+
Atomics.store(signal, SYNC_BRIDGE_SIGNAL_LENGTH_INDEX, 0);
|
|
1144
|
+
postSyncRequest({
|
|
1145
|
+
type: "sync-request",
|
|
1146
|
+
requestId: nextRequestId++,
|
|
1147
|
+
executionId: activeExecutionId,
|
|
1148
|
+
processRequestId: activeProcessRequestId,
|
|
1149
|
+
operation,
|
|
1150
|
+
args,
|
|
1151
|
+
});
|
|
1152
|
+
while (true) {
|
|
1153
|
+
const result = Atomics.wait(signal, SYNC_BRIDGE_SIGNAL_STATE_INDEX, SYNC_BRIDGE_SIGNAL_STATE_IDLE, timeoutMs);
|
|
1154
|
+
if (result !== "timed-out") {
|
|
1155
|
+
break;
|
|
1156
|
+
}
|
|
1157
|
+
throw new Error(`Browser runtime sync bridge timed out while handling ${operation}`);
|
|
1158
|
+
}
|
|
1159
|
+
const status = Atomics.load(signal, SYNC_BRIDGE_SIGNAL_STATUS_INDEX);
|
|
1160
|
+
const kind = Atomics.load(signal, SYNC_BRIDGE_SIGNAL_KIND_INDEX);
|
|
1161
|
+
const length = Atomics.load(signal, SYNC_BRIDGE_SIGNAL_LENGTH_INDEX);
|
|
1162
|
+
const bytes = readBytes(length);
|
|
1163
|
+
Atomics.store(signal, SYNC_BRIDGE_SIGNAL_STATE_INDEX, SYNC_BRIDGE_SIGNAL_STATE_IDLE);
|
|
1164
|
+
if (status === SYNC_BRIDGE_STATUS_ERROR) {
|
|
1165
|
+
const errorPayload = JSON.parse(decoder.decode(bytes));
|
|
1166
|
+
const error = new Error(errorPayload.message);
|
|
1167
|
+
if (errorPayload.code) {
|
|
1168
|
+
error.code = errorPayload.code;
|
|
1169
|
+
}
|
|
1170
|
+
throw error;
|
|
1171
|
+
}
|
|
1172
|
+
return { kind, bytes };
|
|
1173
|
+
}
|
|
1174
|
+
return {
|
|
1175
|
+
requestVoid(operation, args) {
|
|
1176
|
+
requestRaw(operation, args);
|
|
1177
|
+
},
|
|
1178
|
+
requestText(operation, args) {
|
|
1179
|
+
const result = requestRaw(operation, args);
|
|
1180
|
+
if (result.kind !== SYNC_BRIDGE_KIND_TEXT) {
|
|
1181
|
+
throw new Error(`Expected text response from ${operation}, received kind ${result.kind}`);
|
|
1182
|
+
}
|
|
1183
|
+
return decoder.decode(result.bytes);
|
|
1184
|
+
},
|
|
1185
|
+
requestNullableText(operation, args) {
|
|
1186
|
+
const result = requestRaw(operation, args);
|
|
1187
|
+
if (result.kind === SYNC_BRIDGE_KIND_NONE) {
|
|
1188
|
+
return null;
|
|
1189
|
+
}
|
|
1190
|
+
if (result.kind !== SYNC_BRIDGE_KIND_TEXT) {
|
|
1191
|
+
throw new Error(`Expected text response from ${operation}, received kind ${result.kind}`);
|
|
1192
|
+
}
|
|
1193
|
+
return decoder.decode(result.bytes);
|
|
1194
|
+
},
|
|
1195
|
+
requestBinary(operation, args) {
|
|
1196
|
+
const result = requestRaw(operation, args);
|
|
1197
|
+
if (result.kind !== SYNC_BRIDGE_KIND_BINARY) {
|
|
1198
|
+
throw new Error(`Expected binary response from ${operation}, received kind ${result.kind}`);
|
|
1199
|
+
}
|
|
1200
|
+
return result.bytes;
|
|
1201
|
+
},
|
|
1202
|
+
requestJson(operation, args) {
|
|
1203
|
+
const result = requestRaw(operation, args);
|
|
1204
|
+
if (result.kind !== SYNC_BRIDGE_KIND_JSON) {
|
|
1205
|
+
throw new Error(`Expected JSON response from ${operation}, received kind ${result.kind}`);
|
|
1206
|
+
}
|
|
1207
|
+
return JSON.parse(decoder.decode(result.bytes));
|
|
1208
|
+
},
|
|
1209
|
+
};
|
|
1210
|
+
}
|
|
1211
|
+
/**
|
|
1212
|
+
* Initialize the worker-side runtime: set up filesystem, network, bridge
|
|
1213
|
+
* globals, and load the bridge bundle. Called once before any exec/run.
|
|
1214
|
+
*/
|
|
1215
|
+
async function initRuntime(payload) {
|
|
1216
|
+
if (initialized)
|
|
1217
|
+
return;
|
|
1218
|
+
assertBrowserSyncBridgeSupport();
|
|
1219
|
+
captureTimingGlobals();
|
|
1220
|
+
if (!payload.syncBridge) {
|
|
1221
|
+
throw new Error("Browser runtime sync bridge is required for filesystem and module loading parity");
|
|
1222
|
+
}
|
|
1223
|
+
permissions = revivePermissions(payload.permissions);
|
|
1224
|
+
const syncBridge = createSyncBridgeClient(payload.syncBridge);
|
|
1225
|
+
activeSyncBridge = syncBridge;
|
|
1226
|
+
// Apply payload limits (use defaults if not configured)
|
|
1227
|
+
base64TransferLimitBytes =
|
|
1228
|
+
payload.payloadLimits?.base64TransferBytes ?? DEFAULT_BASE64_TRANSFER_BYTES;
|
|
1229
|
+
jsonPayloadLimitBytes =
|
|
1230
|
+
payload.payloadLimits?.jsonPayloadBytes ?? DEFAULT_JSON_PAYLOAD_BYTES;
|
|
1231
|
+
if (payload.networkEnabled) {
|
|
1232
|
+
networkAdapter = wrapNetworkAdapter(createBrowserNetworkAdapter(), permissions);
|
|
1233
|
+
}
|
|
1234
|
+
else {
|
|
1235
|
+
networkAdapter = createNetworkStub();
|
|
1236
|
+
}
|
|
1237
|
+
const processConfig = payload.processConfig ?? {};
|
|
1238
|
+
runtimeProcessConfig = processConfig;
|
|
1239
|
+
runtimeTimingMitigation =
|
|
1240
|
+
payload.timingMitigation ??
|
|
1241
|
+
processConfig.timingMitigation ??
|
|
1242
|
+
runtimeTimingMitigation;
|
|
1243
|
+
processConfig.env = filterEnv(processConfig.env, permissions);
|
|
1244
|
+
processConfig.timingMitigation = runtimeTimingMitigation;
|
|
1245
|
+
delete processConfig.frozenTimeMs;
|
|
1246
|
+
exposeCustomGlobal("_processConfig", processConfig);
|
|
1247
|
+
const osConfig = payload.osConfig ?? {};
|
|
1248
|
+
exposeCustomGlobal("_osConfig", osConfig);
|
|
1249
|
+
exposeCustomGlobal("__agentOsVirtualOs", osConfig);
|
|
1250
|
+
exposeCustomGlobal("_log", makeApplySync((...args) => {
|
|
1251
|
+
emitActiveStdio("stdout", args);
|
|
1252
|
+
}));
|
|
1253
|
+
exposeCustomGlobal("_error", makeApplySync((...args) => {
|
|
1254
|
+
emitActiveStdio("stderr", args);
|
|
1255
|
+
}));
|
|
1256
|
+
// Set up filesystem bridge globals before loading runtime shims.
|
|
1257
|
+
const readFileRef = makeApplySync((path) => {
|
|
1258
|
+
const text = syncBridge.requestText("fs.readFile", [path]);
|
|
1259
|
+
assertTextPayloadSize(`fs.readFile ${path}`, text, jsonPayloadLimitBytes);
|
|
1260
|
+
return text;
|
|
1261
|
+
});
|
|
1262
|
+
const writeFileRef = makeApplySync((path, content) => {
|
|
1263
|
+
assertTextPayloadSize(`fs.writeFile ${path}`, content, jsonPayloadLimitBytes);
|
|
1264
|
+
syncBridge.requestVoid("fs.writeFile", [path, content]);
|
|
1265
|
+
});
|
|
1266
|
+
const readFileBinaryRef = makeApplySync((path) => {
|
|
1267
|
+
const data = syncBridge.requestBinary("fs.readFileBinary", [path]);
|
|
1268
|
+
assertPayloadByteLength(`fs.readFileBinary ${path}`, data.byteLength, base64TransferLimitBytes);
|
|
1269
|
+
return data;
|
|
1270
|
+
});
|
|
1271
|
+
const writeFileBinaryRef = makeApplySync((path, binaryContent) => {
|
|
1272
|
+
assertPayloadByteLength(`fs.writeFileBinary ${path}`, binaryContent.byteLength, base64TransferLimitBytes);
|
|
1273
|
+
syncBridge.requestVoid("fs.writeFileBinary", [path, binaryContent]);
|
|
1274
|
+
});
|
|
1275
|
+
const readDirRef = makeApplySync((path) => {
|
|
1276
|
+
const json = JSON.stringify(syncBridge.requestJson("fs.readDir", [path]));
|
|
1277
|
+
assertTextPayloadSize(`fs.readDir ${path}`, json, jsonPayloadLimitBytes);
|
|
1278
|
+
return json;
|
|
1279
|
+
});
|
|
1280
|
+
const mkdirRef = makeApplySync((path) => {
|
|
1281
|
+
syncBridge.requestVoid("fs.mkdir", [path]);
|
|
1282
|
+
});
|
|
1283
|
+
const rmdirRef = makeApplySync((path) => {
|
|
1284
|
+
syncBridge.requestVoid("fs.rmdir", [path]);
|
|
1285
|
+
});
|
|
1286
|
+
const existsRef = makeApplySync((path) => {
|
|
1287
|
+
return syncBridge.requestJson("fs.exists", [path]);
|
|
1288
|
+
});
|
|
1289
|
+
const statRef = makeApplySync((path) => {
|
|
1290
|
+
return JSON.stringify(syncBridge.requestJson("fs.stat", [path]));
|
|
1291
|
+
});
|
|
1292
|
+
const unlinkRef = makeApplySync((path) => {
|
|
1293
|
+
syncBridge.requestVoid("fs.unlink", [path]);
|
|
1294
|
+
});
|
|
1295
|
+
const renameRef = makeApplySync((oldPath, newPath) => {
|
|
1296
|
+
syncBridge.requestVoid("fs.rename", [oldPath, newPath]);
|
|
1297
|
+
});
|
|
1298
|
+
exposeCustomGlobal("_fs", {
|
|
1299
|
+
readFile: readFileRef,
|
|
1300
|
+
writeFile: writeFileRef,
|
|
1301
|
+
readFileBinary: readFileBinaryRef,
|
|
1302
|
+
writeFileBinary: writeFileBinaryRef,
|
|
1303
|
+
readDir: readDirRef,
|
|
1304
|
+
mkdir: mkdirRef,
|
|
1305
|
+
rmdir: rmdirRef,
|
|
1306
|
+
exists: existsRef,
|
|
1307
|
+
stat: statRef,
|
|
1308
|
+
unlink: unlinkRef,
|
|
1309
|
+
rename: renameRef,
|
|
1310
|
+
});
|
|
1311
|
+
exposeCustomGlobal("_loadPolyfill", makeApplySync((moduleName) => {
|
|
1312
|
+
const name = moduleName.replace(/^node:/, "");
|
|
1313
|
+
const polyfillMap = POLYFILL_CODE_MAP;
|
|
1314
|
+
return polyfillMap[name] ?? null;
|
|
1315
|
+
}));
|
|
1316
|
+
const resolveModuleSync = (request, fromDir, mode) => {
|
|
1317
|
+
return syncBridge.requestNullableText("module.resolve", [
|
|
1318
|
+
request,
|
|
1319
|
+
fromDir,
|
|
1320
|
+
mode ?? "require",
|
|
1321
|
+
]);
|
|
1322
|
+
};
|
|
1323
|
+
const loadFileSync = (path, _mode) => {
|
|
1324
|
+
const source = syncBridge.requestNullableText("module.loadFile", [path]);
|
|
1325
|
+
if (source === null) {
|
|
1326
|
+
return null;
|
|
1327
|
+
}
|
|
1328
|
+
let code = source;
|
|
1329
|
+
if (isESM(source, path)) {
|
|
1330
|
+
code = transform(code, { transforms: ["imports"] }).code;
|
|
1331
|
+
}
|
|
1332
|
+
return transformDynamicImport(code);
|
|
1333
|
+
};
|
|
1334
|
+
const moduleFormatSync = (path) => {
|
|
1335
|
+
return syncBridge.requestNullableText("module.format", [path]);
|
|
1336
|
+
};
|
|
1337
|
+
const batchResolveModulesSync = (requests) => {
|
|
1338
|
+
return syncBridge.requestJson("module.batchResolve", [requests]);
|
|
1339
|
+
};
|
|
1340
|
+
exposeCustomGlobal("_resolveModuleSync", makeApplySync(resolveModuleSync));
|
|
1341
|
+
exposeCustomGlobal("_loadFileSync", makeApplySync(loadFileSync));
|
|
1342
|
+
exposeCustomGlobal("_resolveModule", makeApplySync(resolveModuleSync));
|
|
1343
|
+
exposeCustomGlobal("_loadFile", makeApplySync(loadFileSync));
|
|
1344
|
+
exposeCustomGlobal("_moduleFormat", makeApplySync(moduleFormatSync));
|
|
1345
|
+
exposeCustomGlobal("_batchResolveModules", makeApplySync(batchResolveModulesSync));
|
|
1346
|
+
const randomBytes = (length) => {
|
|
1347
|
+
if (!Number.isInteger(length) || length < 0) {
|
|
1348
|
+
throw new Error("crypto random byte length must be a non-negative integer");
|
|
1349
|
+
}
|
|
1350
|
+
const bytes = new Uint8Array(length);
|
|
1351
|
+
const crypto = globalThis.crypto;
|
|
1352
|
+
if (!crypto?.getRandomValues) {
|
|
1353
|
+
throw new Error("Browser runtime crypto requires getRandomValues support");
|
|
1354
|
+
}
|
|
1355
|
+
for (let offset = 0; offset < bytes.length; offset += 65536) {
|
|
1356
|
+
crypto.getRandomValues(bytes.subarray(offset, offset + 65536));
|
|
1357
|
+
}
|
|
1358
|
+
return bytes;
|
|
1359
|
+
};
|
|
1360
|
+
exposeCustomGlobal("_cryptoRandomFill", makeApplySync((length) => randomBytes(Number(length))));
|
|
1361
|
+
exposeCustomGlobal("_cryptoRandomUUID", makeApplySync(() => {
|
|
1362
|
+
if (typeof globalThis.crypto?.randomUUID !== "function") {
|
|
1363
|
+
throw new Error("Browser runtime crypto requires randomUUID support");
|
|
1364
|
+
}
|
|
1365
|
+
return globalThis.crypto.randomUUID();
|
|
1366
|
+
}));
|
|
1367
|
+
exposeCustomGlobal("_cryptoHashDigest", makeApplySync((algorithm, data) => {
|
|
1368
|
+
return hashDigestBytes(algorithm, data);
|
|
1369
|
+
}));
|
|
1370
|
+
exposeCustomGlobal("_cryptoHmacDigest", makeApplySync((algorithm, key, data) => {
|
|
1371
|
+
return hmacDigestBytes(algorithm, key, data);
|
|
1372
|
+
}));
|
|
1373
|
+
exposeCustomGlobal("_cryptoPbkdf2", makeApplySync((password, salt, iterations, keyLength, algorithm) => {
|
|
1374
|
+
return pbkdf2Bytes(password, salt, iterations, keyLength, algorithm);
|
|
1375
|
+
}));
|
|
1376
|
+
exposeCustomGlobal("_cryptoScrypt", makeApplySync((password, salt, keyLength, options) => {
|
|
1377
|
+
return nobleScrypt(toUint8Array(password), toUint8Array(salt), normalizeScryptOptions(options, keyLength));
|
|
1378
|
+
}));
|
|
1379
|
+
exposeCustomGlobal("_cryptoCipheriv", makeApplySync((algorithm, key, iv, data, optionsJson) => browserCipheriv(algorithm, key, iv, data, optionsJson)));
|
|
1380
|
+
exposeCustomGlobal("_cryptoDecipheriv", makeApplySync((algorithm, key, iv, data, optionsJson) => browserDecipheriv(algorithm, key, iv, data, optionsJson)));
|
|
1381
|
+
exposeCustomGlobal("_cryptoCipherivCreate", makeApplySync(() => unsupportedBrowserCrypto("_cryptoCipherivCreate")));
|
|
1382
|
+
exposeCustomGlobal("_cryptoCipherivUpdate", makeApplySync(() => unsupportedBrowserCrypto("_cryptoCipherivUpdate")));
|
|
1383
|
+
exposeCustomGlobal("_cryptoCipherivFinal", makeApplySync(() => unsupportedBrowserCrypto("_cryptoCipherivFinal")));
|
|
1384
|
+
exposeCustomGlobal("_cryptoSign", makeApplySync((algorithm, data, key) => browserRsaSign(algorithm, data, key)));
|
|
1385
|
+
exposeCustomGlobal("_cryptoVerify", makeApplySync((algorithm, data, key, signature) => browserRsaVerify(algorithm, data, key, signature)));
|
|
1386
|
+
exposeCustomGlobal("_cryptoAsymmetricOp", makeApplySync((operation, key, data, optionsJson) => browserRsaAsymmetricOp(operation, key, data, optionsJson)));
|
|
1387
|
+
exposeCustomGlobal("_cryptoCreateKeyObject", makeApplySync(() => unsupportedBrowserCrypto("_cryptoCreateKeyObject")));
|
|
1388
|
+
exposeCustomGlobal("_cryptoGenerateKeyPairSync", makeApplySync(() => unsupportedBrowserCrypto("_cryptoGenerateKeyPairSync")));
|
|
1389
|
+
exposeCustomGlobal("_cryptoGenerateKeySync", makeApplySync(() => unsupportedBrowserCrypto("_cryptoGenerateKeySync")));
|
|
1390
|
+
exposeCustomGlobal("_cryptoGeneratePrimeSync", makeApplySync(() => unsupportedBrowserCrypto("_cryptoGeneratePrimeSync")));
|
|
1391
|
+
exposeCustomGlobal("_cryptoDiffieHellman", makeApplySync(() => unsupportedBrowserCrypto("_cryptoDiffieHellman")));
|
|
1392
|
+
exposeCustomGlobal("_cryptoDiffieHellmanGroup", makeApplySync(() => unsupportedBrowserCrypto("_cryptoDiffieHellmanGroup")));
|
|
1393
|
+
exposeCustomGlobal("_cryptoDiffieHellmanSessionCreate", makeApplySync(() => unsupportedBrowserCrypto("_cryptoDiffieHellmanSessionCreate")));
|
|
1394
|
+
exposeCustomGlobal("_cryptoDiffieHellmanSessionCall", makeApplySync(() => unsupportedBrowserCrypto("_cryptoDiffieHellmanSessionCall")));
|
|
1395
|
+
exposeCustomGlobal("_cryptoDiffieHellmanSessionDestroy", makeApplySync(() => unsupportedBrowserCrypto("_cryptoDiffieHellmanSessionDestroy")));
|
|
1396
|
+
exposeCustomGlobal("_cryptoSubtle", makeApplySync(() => unsupportedBrowserCrypto("_cryptoSubtle")));
|
|
1397
|
+
exposeCustomGlobal("_scheduleTimer", {
|
|
1398
|
+
apply(_ctx, args) {
|
|
1399
|
+
return new Promise((resolve) => {
|
|
1400
|
+
setTimeout(resolve, args[0]);
|
|
1401
|
+
});
|
|
1402
|
+
},
|
|
1403
|
+
});
|
|
1404
|
+
const netAdapter = networkAdapter ?? createNetworkStub();
|
|
1405
|
+
exposeCustomGlobal("_networkFetchRaw", makeApplyPromise(async (url, optionsJson) => {
|
|
1406
|
+
const options = JSON.parse(optionsJson);
|
|
1407
|
+
const result = await netAdapter.fetch(url, options);
|
|
1408
|
+
return JSON.stringify(result);
|
|
1409
|
+
}));
|
|
1410
|
+
exposeCustomGlobal("_networkDnsLookupRaw", makeApplyPromise(async (request) => {
|
|
1411
|
+
const hostname = typeof request === "string" ? request : String(request.hostname ?? "");
|
|
1412
|
+
const result = await netAdapter.dnsLookup(hostname);
|
|
1413
|
+
if (result.error) {
|
|
1414
|
+
const error = new Error(result.error);
|
|
1415
|
+
error.code = result.code;
|
|
1416
|
+
throw error;
|
|
1417
|
+
}
|
|
1418
|
+
return JSON.stringify(result);
|
|
1419
|
+
}));
|
|
1420
|
+
exposeCustomGlobal("_childProcessSpawnStart", makeApplySync((request) => {
|
|
1421
|
+
return syncBridge.requestJson("child_process.spawn", [
|
|
1422
|
+
request,
|
|
1423
|
+
]);
|
|
1424
|
+
}));
|
|
1425
|
+
exposeCustomGlobal("_childProcessPoll", makeApplySync((sessionId, _waitMs) => {
|
|
1426
|
+
return syncBridge.requestJson("child_process.poll", [sessionId, _waitMs ?? 0]);
|
|
1427
|
+
}));
|
|
1428
|
+
exposeCustomGlobal("_childProcessStdinWrite", makeApplySync((sessionId, data) => {
|
|
1429
|
+
syncBridge.requestVoid("child_process.write_stdin", [sessionId, data]);
|
|
1430
|
+
}));
|
|
1431
|
+
exposeCustomGlobal("_childProcessStdinClose", makeApplySync((sessionId) => {
|
|
1432
|
+
syncBridge.requestVoid("child_process.close_stdin", [sessionId]);
|
|
1433
|
+
}));
|
|
1434
|
+
exposeCustomGlobal("_childProcessKill", makeApplySync((sessionId, signal) => {
|
|
1435
|
+
syncBridge.requestVoid("child_process.kill", [sessionId, signal]);
|
|
1436
|
+
}));
|
|
1437
|
+
exposeCustomGlobal("_childProcessSpawnSync", makeApplySync((request) => {
|
|
1438
|
+
return syncBridge.requestText("child_process.spawn_sync", [
|
|
1439
|
+
request,
|
|
1440
|
+
]);
|
|
1441
|
+
}));
|
|
1442
|
+
exposeCustomGlobal("_processSignalState", makeApplySync((signal, action, maskJson, flags) => {
|
|
1443
|
+
syncBridge.requestVoid("process.signal_state", [
|
|
1444
|
+
signal,
|
|
1445
|
+
action,
|
|
1446
|
+
maskJson,
|
|
1447
|
+
flags,
|
|
1448
|
+
]);
|
|
1449
|
+
}));
|
|
1450
|
+
exposeCustomGlobal("_dgramSocketCreateRaw", makeApplySync((options) => {
|
|
1451
|
+
return syncBridge.requestJson("dgram.create", [options]);
|
|
1452
|
+
}));
|
|
1453
|
+
exposeCustomGlobal("_dgramSocketBindRaw", makeApplySync((socketId, options) => {
|
|
1454
|
+
return syncBridge.requestJson("dgram.bind", [socketId, options]);
|
|
1455
|
+
}));
|
|
1456
|
+
exposeCustomGlobal("_dgramSocketRecvRaw", makeApplySync((socketId, waitMs) => {
|
|
1457
|
+
return syncBridge.requestJson("dgram.recv", [socketId, waitMs ?? 0]);
|
|
1458
|
+
}));
|
|
1459
|
+
exposeCustomGlobal("_dgramSocketSendRaw", makeApplySync((socketId, data, target) => {
|
|
1460
|
+
return syncBridge.requestJson("dgram.send", [socketId, data, target]);
|
|
1461
|
+
}));
|
|
1462
|
+
exposeCustomGlobal("_dgramSocketCloseRaw", makeApplySync((socketId) => {
|
|
1463
|
+
return syncBridge.requestJson("dgram.close", [socketId]);
|
|
1464
|
+
}));
|
|
1465
|
+
exposeCustomGlobal("_dgramSocketAddressRaw", makeApplySync((socketId) => {
|
|
1466
|
+
return syncBridge.requestJson("dgram.address", [socketId]);
|
|
1467
|
+
}));
|
|
1468
|
+
exposeCustomGlobal("_dgramSocketSetBufferSizeRaw", makeApplySync((socketId, which, size) => {
|
|
1469
|
+
return syncBridge.requestJson("dgram.setBufferSize", [
|
|
1470
|
+
socketId,
|
|
1471
|
+
which,
|
|
1472
|
+
size,
|
|
1473
|
+
]);
|
|
1474
|
+
}));
|
|
1475
|
+
exposeCustomGlobal("_dgramSocketGetBufferSizeRaw", makeApplySync((socketId, which) => {
|
|
1476
|
+
return syncBridge.requestJson("dgram.getBufferSize", [socketId, which]);
|
|
1477
|
+
}));
|
|
1478
|
+
exposeCustomGlobal("_fsModule", createFsModule(syncBridge));
|
|
1479
|
+
exposeMutableRuntimeStateGlobal("_moduleCache", {});
|
|
1480
|
+
exposeMutableRuntimeStateGlobal("_pendingModules", {});
|
|
1481
|
+
exposeMutableRuntimeStateGlobal("_currentModule", { dirname: "/" });
|
|
1482
|
+
globalEval(getRequireSetupCode());
|
|
1483
|
+
ensureProcessGlobal();
|
|
1484
|
+
// Block dangerous Web APIs that bypass bridge permission checks
|
|
1485
|
+
const dangerousApis = [
|
|
1486
|
+
"XMLHttpRequest",
|
|
1487
|
+
"WebSocket",
|
|
1488
|
+
"importScripts",
|
|
1489
|
+
"indexedDB",
|
|
1490
|
+
"caches",
|
|
1491
|
+
"BroadcastChannel",
|
|
1492
|
+
];
|
|
1493
|
+
for (const api of dangerousApis) {
|
|
1494
|
+
try {
|
|
1495
|
+
delete self[api];
|
|
1496
|
+
}
|
|
1497
|
+
catch {
|
|
1498
|
+
// May not exist or may be non-configurable
|
|
1499
|
+
}
|
|
1500
|
+
Object.defineProperty(self, api, {
|
|
1501
|
+
get() {
|
|
1502
|
+
throw new ReferenceError(`${api} is not available in sandbox`);
|
|
1503
|
+
},
|
|
1504
|
+
configurable: false,
|
|
1505
|
+
});
|
|
1506
|
+
}
|
|
1507
|
+
// Lock down self.onmessage so sandbox code cannot hijack the control channel
|
|
1508
|
+
const currentHandler = self.onmessage;
|
|
1509
|
+
Object.defineProperty(self, "onmessage", {
|
|
1510
|
+
value: currentHandler,
|
|
1511
|
+
writable: false,
|
|
1512
|
+
configurable: false,
|
|
1513
|
+
});
|
|
1514
|
+
// Block self.postMessage so sandbox code cannot forge responses to host
|
|
1515
|
+
Object.defineProperty(self, "postMessage", {
|
|
1516
|
+
get() {
|
|
1517
|
+
throw new TypeError("postMessage is not available in sandbox");
|
|
1518
|
+
},
|
|
1519
|
+
configurable: false,
|
|
1520
|
+
});
|
|
1521
|
+
initialized = true;
|
|
1522
|
+
}
|
|
1523
|
+
function resetModuleState(cwd) {
|
|
1524
|
+
exposeMutableRuntimeStateGlobal("_moduleCache", {});
|
|
1525
|
+
exposeMutableRuntimeStateGlobal("_pendingModules", {});
|
|
1526
|
+
exposeMutableRuntimeStateGlobal("_currentModule", { dirname: cwd });
|
|
1527
|
+
}
|
|
1528
|
+
function setDynamicImportFallback() {
|
|
1529
|
+
exposeMutableRuntimeStateGlobal("__dynamicImport", (specifier) => {
|
|
1530
|
+
const cached = dynamicImportCache.get(specifier);
|
|
1531
|
+
if (cached)
|
|
1532
|
+
return Promise.resolve(cached);
|
|
1533
|
+
try {
|
|
1534
|
+
const runtimeRequire = globalThis.require;
|
|
1535
|
+
if (typeof runtimeRequire !== "function") {
|
|
1536
|
+
throw new Error("require is not available in browser runtime");
|
|
1537
|
+
}
|
|
1538
|
+
const mod = runtimeRequire(specifier);
|
|
1539
|
+
return Promise.resolve({
|
|
1540
|
+
default: mod,
|
|
1541
|
+
...mod,
|
|
1542
|
+
});
|
|
1543
|
+
}
|
|
1544
|
+
catch (e) {
|
|
1545
|
+
return Promise.reject(new Error(`Cannot dynamically import '${specifier}': ${String(e)}`));
|
|
1546
|
+
}
|
|
1547
|
+
});
|
|
1548
|
+
}
|
|
1549
|
+
function toProcessChunk(value, encoding) {
|
|
1550
|
+
if (encoding) {
|
|
1551
|
+
return value;
|
|
1552
|
+
}
|
|
1553
|
+
return encoder.encode(value);
|
|
1554
|
+
}
|
|
1555
|
+
function normalizeProcessOutputChunk(chunk) {
|
|
1556
|
+
if (typeof chunk === "string") {
|
|
1557
|
+
return chunk;
|
|
1558
|
+
}
|
|
1559
|
+
if (chunk instanceof Uint8Array) {
|
|
1560
|
+
return decoder.decode(chunk);
|
|
1561
|
+
}
|
|
1562
|
+
if (ArrayBuffer.isView(chunk)) {
|
|
1563
|
+
return decoder.decode(new Uint8Array(chunk.buffer, chunk.byteOffset, chunk.byteLength));
|
|
1564
|
+
}
|
|
1565
|
+
if (chunk instanceof ArrayBuffer) {
|
|
1566
|
+
return decoder.decode(new Uint8Array(chunk));
|
|
1567
|
+
}
|
|
1568
|
+
return String(chunk);
|
|
1569
|
+
}
|
|
1570
|
+
function emitProcessStdio(channel, chunk) {
|
|
1571
|
+
if (activeProcessRequestId === null || activeExecutionId === null) {
|
|
1572
|
+
return true;
|
|
1573
|
+
}
|
|
1574
|
+
emitStdio(activeExecutionId, activeProcessRequestId, channel, normalizeProcessOutputChunk(chunk));
|
|
1575
|
+
return true;
|
|
1576
|
+
}
|
|
1577
|
+
function createBrowserProcess() {
|
|
1578
|
+
let cwd = "/";
|
|
1579
|
+
let stdinData = "";
|
|
1580
|
+
let stdinPosition = 0;
|
|
1581
|
+
let stdinEnded = false;
|
|
1582
|
+
let stdinFlushQueued = false;
|
|
1583
|
+
const stdinListeners = Object.create(null);
|
|
1584
|
+
const stdinOnceListeners = Object.create(null);
|
|
1585
|
+
const emitStdinListeners = (event, value) => {
|
|
1586
|
+
const listeners = [
|
|
1587
|
+
...(stdinListeners[event] ?? []),
|
|
1588
|
+
...(stdinOnceListeners[event] ?? []),
|
|
1589
|
+
];
|
|
1590
|
+
stdinOnceListeners[event] = [];
|
|
1591
|
+
for (const listener of listeners) {
|
|
1592
|
+
listener(value);
|
|
1593
|
+
}
|
|
1594
|
+
return listeners.length > 0;
|
|
1595
|
+
};
|
|
1596
|
+
const clearStdinListeners = () => {
|
|
1597
|
+
for (const key of Object.keys(stdinListeners)) {
|
|
1598
|
+
stdinListeners[key] = [];
|
|
1599
|
+
}
|
|
1600
|
+
for (const key of Object.keys(stdinOnceListeners)) {
|
|
1601
|
+
stdinOnceListeners[key] = [];
|
|
1602
|
+
}
|
|
1603
|
+
};
|
|
1604
|
+
const flushStdin = () => {
|
|
1605
|
+
stdinFlushQueued = false;
|
|
1606
|
+
if (stdin.paused || stdinEnded) {
|
|
1607
|
+
return;
|
|
1608
|
+
}
|
|
1609
|
+
if (stdinPosition < stdinData.length) {
|
|
1610
|
+
const chunk = stdinData.slice(stdinPosition);
|
|
1611
|
+
stdinPosition = stdinData.length;
|
|
1612
|
+
emitStdinListeners("data", toProcessChunk(chunk, stdin.encoding));
|
|
1613
|
+
}
|
|
1614
|
+
if (!stdinEnded) {
|
|
1615
|
+
stdinEnded = true;
|
|
1616
|
+
emitStdinListeners("end");
|
|
1617
|
+
emitStdinListeners("close");
|
|
1618
|
+
}
|
|
1619
|
+
};
|
|
1620
|
+
const scheduleStdinFlush = () => {
|
|
1621
|
+
if (stdinFlushQueued) {
|
|
1622
|
+
return;
|
|
1623
|
+
}
|
|
1624
|
+
stdinFlushQueued = true;
|
|
1625
|
+
queueMicrotask(flushStdin);
|
|
1626
|
+
};
|
|
1627
|
+
const stdin = {
|
|
1628
|
+
readable: true,
|
|
1629
|
+
paused: true,
|
|
1630
|
+
encoding: null,
|
|
1631
|
+
isRaw: false,
|
|
1632
|
+
read(size) {
|
|
1633
|
+
if (stdinPosition >= stdinData.length) {
|
|
1634
|
+
return null;
|
|
1635
|
+
}
|
|
1636
|
+
const chunk = size
|
|
1637
|
+
? stdinData.slice(stdinPosition, stdinPosition + size)
|
|
1638
|
+
: stdinData.slice(stdinPosition);
|
|
1639
|
+
stdinPosition += chunk.length;
|
|
1640
|
+
return toProcessChunk(chunk, stdin.encoding);
|
|
1641
|
+
},
|
|
1642
|
+
on(event, listener) {
|
|
1643
|
+
if (!stdinListeners[event]) {
|
|
1644
|
+
stdinListeners[event] = [];
|
|
1645
|
+
}
|
|
1646
|
+
stdinListeners[event].push(listener);
|
|
1647
|
+
if (event === "data" && stdin.paused) {
|
|
1648
|
+
stdin.resume();
|
|
1649
|
+
}
|
|
1650
|
+
return stdin;
|
|
1651
|
+
},
|
|
1652
|
+
once(event, listener) {
|
|
1653
|
+
if (!stdinOnceListeners[event]) {
|
|
1654
|
+
stdinOnceListeners[event] = [];
|
|
1655
|
+
}
|
|
1656
|
+
stdinOnceListeners[event].push(listener);
|
|
1657
|
+
if (event === "data" && stdin.paused) {
|
|
1658
|
+
stdin.resume();
|
|
1659
|
+
}
|
|
1660
|
+
return stdin;
|
|
1661
|
+
},
|
|
1662
|
+
off(event, listener) {
|
|
1663
|
+
if (!stdinListeners[event]) {
|
|
1664
|
+
return stdin;
|
|
1665
|
+
}
|
|
1666
|
+
stdinListeners[event] = stdinListeners[event].filter((candidate) => candidate !== listener);
|
|
1667
|
+
return stdin;
|
|
1668
|
+
},
|
|
1669
|
+
removeListener(event, listener) {
|
|
1670
|
+
return stdin.off(event, listener);
|
|
1671
|
+
},
|
|
1672
|
+
emit(event, value) {
|
|
1673
|
+
return emitStdinListeners(event, value);
|
|
1674
|
+
},
|
|
1675
|
+
pause() {
|
|
1676
|
+
stdin.paused = true;
|
|
1677
|
+
return stdin;
|
|
1678
|
+
},
|
|
1679
|
+
resume() {
|
|
1680
|
+
stdin.paused = false;
|
|
1681
|
+
scheduleStdinFlush();
|
|
1682
|
+
return stdin;
|
|
1683
|
+
},
|
|
1684
|
+
setEncoding(encoding) {
|
|
1685
|
+
stdin.encoding = encoding;
|
|
1686
|
+
return stdin;
|
|
1687
|
+
},
|
|
1688
|
+
setRawMode(mode) {
|
|
1689
|
+
stdin.isRaw = mode;
|
|
1690
|
+
return stdin;
|
|
1691
|
+
},
|
|
1692
|
+
get readableLength() {
|
|
1693
|
+
return encoder.encode(stdinData.slice(stdinPosition)).byteLength;
|
|
1694
|
+
},
|
|
1695
|
+
get isTTY() {
|
|
1696
|
+
return false;
|
|
1697
|
+
},
|
|
1698
|
+
async *[Symbol.asyncIterator]() {
|
|
1699
|
+
const remaining = stdinData.slice(stdinPosition);
|
|
1700
|
+
for (const line of remaining.split("\n")) {
|
|
1701
|
+
if (line.length > 0) {
|
|
1702
|
+
yield line;
|
|
1703
|
+
}
|
|
1704
|
+
}
|
|
1705
|
+
},
|
|
1706
|
+
};
|
|
1707
|
+
const processListeners = Object.create(null);
|
|
1708
|
+
const processOnceListeners = Object.create(null);
|
|
1709
|
+
const requireProcessListener = (listener) => {
|
|
1710
|
+
if (typeof listener !== "function") {
|
|
1711
|
+
throw new TypeError("process listener must be a function");
|
|
1712
|
+
}
|
|
1713
|
+
return listener;
|
|
1714
|
+
};
|
|
1715
|
+
const processSignalListenerCount = (event) => {
|
|
1716
|
+
return ((processListeners[event]?.length ?? 0) +
|
|
1717
|
+
(processOnceListeners[event]?.length ?? 0));
|
|
1718
|
+
};
|
|
1719
|
+
const syncProcessSignalState = (event, action) => {
|
|
1720
|
+
const signal = signalNumberForEvent(event);
|
|
1721
|
+
if (signal === null) {
|
|
1722
|
+
return;
|
|
1723
|
+
}
|
|
1724
|
+
const syncBridge = activeSyncBridge;
|
|
1725
|
+
if (!syncBridge) {
|
|
1726
|
+
return;
|
|
1727
|
+
}
|
|
1728
|
+
syncBridge.requestVoid("process.signal_state", [signal, action, "[]", 0]);
|
|
1729
|
+
};
|
|
1730
|
+
const maybeSyncProcessSignalTransition = (event, before, after) => {
|
|
1731
|
+
if (before === 0 && after > 0) {
|
|
1732
|
+
syncProcessSignalState(event, "user");
|
|
1733
|
+
}
|
|
1734
|
+
else if (before > 0 && after === 0) {
|
|
1735
|
+
syncProcessSignalState(event, "default");
|
|
1736
|
+
}
|
|
1737
|
+
};
|
|
1738
|
+
const processOn = (event, listener, once) => {
|
|
1739
|
+
requireProcessListener(listener);
|
|
1740
|
+
const before = processSignalListenerCount(event);
|
|
1741
|
+
const map = once ? processOnceListeners : processListeners;
|
|
1742
|
+
if (!map[event]) {
|
|
1743
|
+
map[event] = [];
|
|
1744
|
+
}
|
|
1745
|
+
map[event].push(listener);
|
|
1746
|
+
maybeSyncProcessSignalTransition(event, before, processSignalListenerCount(event));
|
|
1747
|
+
return processBridge;
|
|
1748
|
+
};
|
|
1749
|
+
const processOff = (event, listener) => {
|
|
1750
|
+
requireProcessListener(listener);
|
|
1751
|
+
const before = processSignalListenerCount(event);
|
|
1752
|
+
if (processListeners[event]) {
|
|
1753
|
+
processListeners[event] = processListeners[event].filter((candidate) => candidate !== listener);
|
|
1754
|
+
}
|
|
1755
|
+
if (processOnceListeners[event]) {
|
|
1756
|
+
processOnceListeners[event] = processOnceListeners[event].filter((candidate) => candidate !== listener);
|
|
1757
|
+
}
|
|
1758
|
+
maybeSyncProcessSignalTransition(event, before, processSignalListenerCount(event));
|
|
1759
|
+
return processBridge;
|
|
1760
|
+
};
|
|
1761
|
+
const emitProcessListeners = (event, value) => {
|
|
1762
|
+
const before = processSignalListenerCount(event);
|
|
1763
|
+
const listeners = [
|
|
1764
|
+
...(processListeners[event] ?? []),
|
|
1765
|
+
...(processOnceListeners[event] ?? []),
|
|
1766
|
+
];
|
|
1767
|
+
processOnceListeners[event] = [];
|
|
1768
|
+
for (const listener of listeners) {
|
|
1769
|
+
listener(value);
|
|
1770
|
+
}
|
|
1771
|
+
maybeSyncProcessSignalTransition(event, before, processSignalListenerCount(event));
|
|
1772
|
+
return listeners.length > 0;
|
|
1773
|
+
};
|
|
1774
|
+
const processBridge = {
|
|
1775
|
+
browser: true,
|
|
1776
|
+
env: {},
|
|
1777
|
+
argv: ["node"],
|
|
1778
|
+
argv0: "node",
|
|
1779
|
+
pid: 1,
|
|
1780
|
+
ppid: 0,
|
|
1781
|
+
uid: 1000,
|
|
1782
|
+
gid: 1000,
|
|
1783
|
+
platform: "browser",
|
|
1784
|
+
arch: "x64",
|
|
1785
|
+
version: "v22.0.0",
|
|
1786
|
+
versions: {
|
|
1787
|
+
node: "22.0.0",
|
|
1788
|
+
// Guest crypto is served by pure-Rust crates, not OpenSSL. We still
|
|
1789
|
+
// surface the OpenSSL release bundled by the emulated Node version so
|
|
1790
|
+
// guests that read process.versions.openssl keep working, and the
|
|
1791
|
+
// native V8 runtime reports the same constant for parity.
|
|
1792
|
+
openssl: "3.0.13+quic",
|
|
1793
|
+
},
|
|
1794
|
+
stdin,
|
|
1795
|
+
stdout: {
|
|
1796
|
+
isTTY: false,
|
|
1797
|
+
write(chunk) {
|
|
1798
|
+
return emitProcessStdio("stdout", chunk);
|
|
1799
|
+
},
|
|
1800
|
+
},
|
|
1801
|
+
stderr: {
|
|
1802
|
+
isTTY: false,
|
|
1803
|
+
write(chunk) {
|
|
1804
|
+
return emitProcessStdio("stderr", chunk);
|
|
1805
|
+
},
|
|
1806
|
+
},
|
|
1807
|
+
exitCode: 0,
|
|
1808
|
+
cwd: () => cwd,
|
|
1809
|
+
chdir: (nextCwd) => {
|
|
1810
|
+
cwd = String(nextCwd);
|
|
1811
|
+
},
|
|
1812
|
+
getuid: () => processBridge.uid,
|
|
1813
|
+
getgid: () => processBridge.gid,
|
|
1814
|
+
geteuid: () => processBridge.uid,
|
|
1815
|
+
getegid: () => processBridge.gid,
|
|
1816
|
+
getgroups: () => [processBridge.gid],
|
|
1817
|
+
nextTick: (callback, ...args) => {
|
|
1818
|
+
queueMicrotask(() => callback(...args));
|
|
1819
|
+
},
|
|
1820
|
+
exit(code) {
|
|
1821
|
+
const exitCode = typeof code === "number" ? code : (processBridge.exitCode ?? 0);
|
|
1822
|
+
processBridge.exitCode = exitCode;
|
|
1823
|
+
throw new Error(`process.exit(${exitCode})`);
|
|
1824
|
+
},
|
|
1825
|
+
on(event, listener) {
|
|
1826
|
+
return processOn(String(event), listener, false);
|
|
1827
|
+
},
|
|
1828
|
+
once(event, listener) {
|
|
1829
|
+
return processOn(String(event), listener, true);
|
|
1830
|
+
},
|
|
1831
|
+
off(event, listener) {
|
|
1832
|
+
return processOff(String(event), listener);
|
|
1833
|
+
},
|
|
1834
|
+
removeListener(event, listener) {
|
|
1835
|
+
return processOff(String(event), listener);
|
|
1836
|
+
},
|
|
1837
|
+
emit(event, value) {
|
|
1838
|
+
return emitProcessListeners(String(event), value);
|
|
1839
|
+
},
|
|
1840
|
+
__secureExecRefreshProcess(nextConfig) {
|
|
1841
|
+
clearStdinListeners();
|
|
1842
|
+
for (const key of Object.keys(processListeners)) {
|
|
1843
|
+
processListeners[key] = [];
|
|
1844
|
+
}
|
|
1845
|
+
for (const key of Object.keys(processOnceListeners)) {
|
|
1846
|
+
processOnceListeners[key] = [];
|
|
1847
|
+
}
|
|
1848
|
+
stdinData = typeof nextConfig?.stdin === "string" ? nextConfig.stdin : "";
|
|
1849
|
+
stdinPosition = 0;
|
|
1850
|
+
stdinEnded = false;
|
|
1851
|
+
stdinFlushQueued = false;
|
|
1852
|
+
stdin.paused = true;
|
|
1853
|
+
stdin.encoding = null;
|
|
1854
|
+
stdin.isRaw = false;
|
|
1855
|
+
processBridge.exitCode = 0;
|
|
1856
|
+
processBridge.env =
|
|
1857
|
+
nextConfig?.env && typeof nextConfig.env === "object"
|
|
1858
|
+
? { ...nextConfig.env }
|
|
1859
|
+
: {};
|
|
1860
|
+
if (typeof nextConfig?.cwd === "string") {
|
|
1861
|
+
cwd = nextConfig.cwd;
|
|
1862
|
+
}
|
|
1863
|
+
processBridge.argv = Array.isArray(nextConfig?.argv)
|
|
1864
|
+
? nextConfig.argv.map((value) => String(value))
|
|
1865
|
+
: ["node"];
|
|
1866
|
+
processBridge.argv0 = processBridge.argv[0] ?? "node";
|
|
1867
|
+
if (typeof nextConfig?.platform === "string") {
|
|
1868
|
+
processBridge.platform = nextConfig.platform;
|
|
1869
|
+
}
|
|
1870
|
+
if (typeof nextConfig?.arch === "string") {
|
|
1871
|
+
processBridge.arch = nextConfig.arch;
|
|
1872
|
+
}
|
|
1873
|
+
if (typeof nextConfig?.version === "string") {
|
|
1874
|
+
processBridge.version = nextConfig.version;
|
|
1875
|
+
processBridge.versions.node = nextConfig.version.replace(/^v/, "");
|
|
1876
|
+
}
|
|
1877
|
+
if (typeof nextConfig?.pid === "number") {
|
|
1878
|
+
processBridge.pid = nextConfig.pid;
|
|
1879
|
+
}
|
|
1880
|
+
if (typeof nextConfig?.ppid === "number") {
|
|
1881
|
+
processBridge.ppid = nextConfig.ppid;
|
|
1882
|
+
}
|
|
1883
|
+
if (typeof nextConfig?.uid === "number") {
|
|
1884
|
+
processBridge.uid = nextConfig.uid;
|
|
1885
|
+
}
|
|
1886
|
+
if (typeof nextConfig?.gid === "number") {
|
|
1887
|
+
processBridge.gid = nextConfig.gid;
|
|
1888
|
+
}
|
|
1889
|
+
},
|
|
1890
|
+
};
|
|
1891
|
+
return processBridge;
|
|
1892
|
+
}
|
|
1893
|
+
function getRuntimeProcess() {
|
|
1894
|
+
const proc = globalThis.process;
|
|
1895
|
+
if (!proc || typeof proc !== "object") {
|
|
1896
|
+
return undefined;
|
|
1897
|
+
}
|
|
1898
|
+
return proc;
|
|
1899
|
+
}
|
|
1900
|
+
function refreshRuntimeProcess() {
|
|
1901
|
+
const proc = getRuntimeProcess();
|
|
1902
|
+
const refresh = proc?.__secureExecRefreshProcess;
|
|
1903
|
+
if (typeof refresh === "function") {
|
|
1904
|
+
refresh(runtimeProcessConfig);
|
|
1905
|
+
}
|
|
1906
|
+
}
|
|
1907
|
+
function ensureProcessGlobal() {
|
|
1908
|
+
if (getRuntimeProcess()) {
|
|
1909
|
+
refreshRuntimeProcess();
|
|
1910
|
+
return;
|
|
1911
|
+
}
|
|
1912
|
+
exposeMutableRuntimeStateGlobal("process", createBrowserProcess());
|
|
1913
|
+
refreshRuntimeProcess();
|
|
1914
|
+
}
|
|
1915
|
+
function updateProcessConfig(options, timingMitigation, frozenTimeMs) {
|
|
1916
|
+
if (runtimeProcessConfig) {
|
|
1917
|
+
runtimeProcessConfig.timingMitigation = timingMitigation;
|
|
1918
|
+
if (frozenTimeMs === undefined) {
|
|
1919
|
+
delete runtimeProcessConfig.frozenTimeMs;
|
|
1920
|
+
}
|
|
1921
|
+
else {
|
|
1922
|
+
runtimeProcessConfig.frozenTimeMs = frozenTimeMs;
|
|
1923
|
+
}
|
|
1924
|
+
runtimeProcessConfig.stdin = options?.stdin ?? "";
|
|
1925
|
+
if (options?.env) {
|
|
1926
|
+
const filtered = filterEnv(options.env, permissions);
|
|
1927
|
+
const currentEnv = runtimeProcessConfig.env && typeof runtimeProcessConfig.env === "object"
|
|
1928
|
+
? runtimeProcessConfig.env
|
|
1929
|
+
: {};
|
|
1930
|
+
runtimeProcessConfig.env = { ...currentEnv, ...filtered };
|
|
1931
|
+
}
|
|
1932
|
+
}
|
|
1933
|
+
refreshRuntimeProcess();
|
|
1934
|
+
const proc = getRuntimeProcess();
|
|
1935
|
+
if (!proc)
|
|
1936
|
+
return;
|
|
1937
|
+
proc.exitCode = 0;
|
|
1938
|
+
proc.timingMitigation = timingMitigation;
|
|
1939
|
+
if (frozenTimeMs === undefined) {
|
|
1940
|
+
delete proc.frozenTimeMs;
|
|
1941
|
+
}
|
|
1942
|
+
else {
|
|
1943
|
+
proc.frozenTimeMs = frozenTimeMs;
|
|
1944
|
+
}
|
|
1945
|
+
if (options?.cwd && typeof proc.chdir === "function") {
|
|
1946
|
+
exposeMutableRuntimeStateGlobal("__runtimeProcessCwdOverride", options.cwd);
|
|
1947
|
+
globalEval(getIsolateRuntimeSource("overrideProcessCwd"));
|
|
1948
|
+
try {
|
|
1949
|
+
proc.chdir(options.cwd);
|
|
1950
|
+
}
|
|
1951
|
+
catch (error) {
|
|
1952
|
+
if (!(error instanceof Error &&
|
|
1953
|
+
error.message.includes("process.chdir() is not supported in workers"))) {
|
|
1954
|
+
throw error;
|
|
1955
|
+
}
|
|
1956
|
+
}
|
|
1957
|
+
}
|
|
1958
|
+
}
|
|
1959
|
+
/**
|
|
1960
|
+
* Execute user code as a script (process-style). Transforms ESM/dynamic
|
|
1961
|
+
* imports, sets up module/exports globals, and waits for active handles.
|
|
1962
|
+
*/
|
|
1963
|
+
async function execScript(executionId, requestId, code, options, captureStdio = false) {
|
|
1964
|
+
resetModuleState(options?.cwd ?? "/");
|
|
1965
|
+
const timingMitigation = options?.timingMitigation ?? runtimeTimingMitigation;
|
|
1966
|
+
const frozenTimeMs = applyTimingMitigation(timingMitigation);
|
|
1967
|
+
updateProcessConfig(options, timingMitigation, frozenTimeMs);
|
|
1968
|
+
setDynamicImportFallback();
|
|
1969
|
+
const previousProcessRequestId = activeProcessRequestId;
|
|
1970
|
+
const previousExecutionId = activeExecutionId;
|
|
1971
|
+
const previousCaptureStdio = activeCaptureStdio;
|
|
1972
|
+
activeProcessRequestId = requestId;
|
|
1973
|
+
activeExecutionId = executionId;
|
|
1974
|
+
activeCaptureStdio = captureStdio;
|
|
1975
|
+
try {
|
|
1976
|
+
const scriptResult = (async () => {
|
|
1977
|
+
let transformed = code;
|
|
1978
|
+
if (isESM(code, options?.filePath)) {
|
|
1979
|
+
transformed = transform(transformed, { transforms: ["imports"] }).code;
|
|
1980
|
+
}
|
|
1981
|
+
transformed = transformDynamicImport(transformed);
|
|
1982
|
+
exposeMutableRuntimeStateGlobal("module", { exports: {} });
|
|
1983
|
+
const moduleRef = globalThis.module;
|
|
1984
|
+
exposeMutableRuntimeStateGlobal("exports", moduleRef.exports);
|
|
1985
|
+
if (options?.filePath) {
|
|
1986
|
+
const dirname = options.filePath.includes("/")
|
|
1987
|
+
? options.filePath.substring(0, options.filePath.lastIndexOf("/")) ||
|
|
1988
|
+
"/"
|
|
1989
|
+
: "/";
|
|
1990
|
+
exposeMutableRuntimeStateGlobal("__filename", options.filePath);
|
|
1991
|
+
exposeMutableRuntimeStateGlobal("__dirname", dirname);
|
|
1992
|
+
exposeMutableRuntimeStateGlobal("_currentModule", {
|
|
1993
|
+
dirname,
|
|
1994
|
+
filename: options.filePath,
|
|
1995
|
+
});
|
|
1996
|
+
}
|
|
1997
|
+
// Await the eval result so async IIFEs / top-level promise expressions
|
|
1998
|
+
// resolve before we check for active handles.
|
|
1999
|
+
const evalResult = globalEval(transformed);
|
|
2000
|
+
if (evalResult &&
|
|
2001
|
+
typeof evalResult === "object" &&
|
|
2002
|
+
typeof evalResult.then === "function") {
|
|
2003
|
+
await evalResult;
|
|
2004
|
+
}
|
|
2005
|
+
await Promise.resolve();
|
|
2006
|
+
const waitForActiveHandles = globalThis
|
|
2007
|
+
._waitForActiveHandles;
|
|
2008
|
+
if (typeof waitForActiveHandles === "function") {
|
|
2009
|
+
await waitForActiveHandles();
|
|
2010
|
+
}
|
|
2011
|
+
const exitCode = globalThis.process
|
|
2012
|
+
?.exitCode ?? 0;
|
|
2013
|
+
return {
|
|
2014
|
+
code: exitCode,
|
|
2015
|
+
};
|
|
2016
|
+
})();
|
|
2017
|
+
const signalResult = new Promise((resolve) => {
|
|
2018
|
+
pendingExecutionSignals.set(executionId, (signal) => {
|
|
2019
|
+
const code = defaultSignalExitCode(signal);
|
|
2020
|
+
resolve({ code: code ?? 0 });
|
|
2021
|
+
});
|
|
2022
|
+
});
|
|
2023
|
+
return await Promise.race([scriptResult, signalResult]);
|
|
2024
|
+
}
|
|
2025
|
+
catch (err) {
|
|
2026
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
2027
|
+
const exitMatch = message.match(/process\.exit\((\d+)\)/);
|
|
2028
|
+
if (exitMatch) {
|
|
2029
|
+
const exitCode = Number.parseInt(exitMatch[1], 10);
|
|
2030
|
+
return {
|
|
2031
|
+
code: exitCode,
|
|
2032
|
+
};
|
|
2033
|
+
}
|
|
2034
|
+
return {
|
|
2035
|
+
code: 1,
|
|
2036
|
+
errorMessage: boundErrorMessage(message),
|
|
2037
|
+
};
|
|
2038
|
+
}
|
|
2039
|
+
finally {
|
|
2040
|
+
pendingExecutionSignals.delete(executionId);
|
|
2041
|
+
activeProcessRequestId = previousProcessRequestId;
|
|
2042
|
+
activeExecutionId = previousExecutionId;
|
|
2043
|
+
activeCaptureStdio = previousCaptureStdio;
|
|
2044
|
+
}
|
|
2045
|
+
}
|
|
2046
|
+
async function runScript(executionId, requestId, code, filePath, captureStdio = false) {
|
|
2047
|
+
const execResult = await execScript(executionId, requestId, code, { filePath }, captureStdio);
|
|
2048
|
+
const moduleObj = globalThis.module;
|
|
2049
|
+
return {
|
|
2050
|
+
...execResult,
|
|
2051
|
+
exports: moduleObj?.exports,
|
|
2052
|
+
};
|
|
2053
|
+
}
|
|
2054
|
+
self.onmessage = async (event) => {
|
|
2055
|
+
const message = event.data;
|
|
2056
|
+
try {
|
|
2057
|
+
if (message.type === "init") {
|
|
2058
|
+
if (typeof message.controlToken !== "string" ||
|
|
2059
|
+
message.controlToken.length === 0) {
|
|
2060
|
+
return;
|
|
2061
|
+
}
|
|
2062
|
+
if (controlToken && message.controlToken !== controlToken) {
|
|
2063
|
+
return;
|
|
2064
|
+
}
|
|
2065
|
+
controlToken = message.controlToken;
|
|
2066
|
+
await initRuntime(message.payload);
|
|
2067
|
+
postResponse({
|
|
2068
|
+
type: "response",
|
|
2069
|
+
id: message.id,
|
|
2070
|
+
ok: true,
|
|
2071
|
+
result: true,
|
|
2072
|
+
});
|
|
2073
|
+
return;
|
|
2074
|
+
}
|
|
2075
|
+
if (!controlToken || message.controlToken !== controlToken) {
|
|
2076
|
+
return;
|
|
2077
|
+
}
|
|
2078
|
+
if (!initialized) {
|
|
2079
|
+
throw new Error("Sandbox worker not initialized");
|
|
2080
|
+
}
|
|
2081
|
+
if (message.type === "exec") {
|
|
2082
|
+
postAsyncResponse(message.id, execScript(message.payload.executionId, message.id, message.payload.code, message.payload.options, message.payload.captureStdio));
|
|
2083
|
+
return;
|
|
2084
|
+
}
|
|
2085
|
+
if (message.type === "run") {
|
|
2086
|
+
postAsyncResponse(message.id, runScript(message.payload.executionId, message.id, message.payload.code, message.payload.filePath, message.payload.captureStdio));
|
|
2087
|
+
return;
|
|
2088
|
+
}
|
|
2089
|
+
if (message.type === "signal") {
|
|
2090
|
+
const signal = Number(message.payload.signal);
|
|
2091
|
+
const resolveSignal = pendingExecutionSignals.get(message.payload.executionId);
|
|
2092
|
+
if (resolveSignal && Number.isInteger(signal)) {
|
|
2093
|
+
resolveSignal(signal);
|
|
2094
|
+
}
|
|
2095
|
+
return;
|
|
2096
|
+
}
|
|
2097
|
+
if (message.type === "extension") {
|
|
2098
|
+
const error = new Error(`Browser worker extension dispatch is not implemented for namespace ${message.payload.namespace}`);
|
|
2099
|
+
error.code = "ERR_SECURE_EXEC_BROWSER_EXTENSION_UNSUPPORTED";
|
|
2100
|
+
throw error;
|
|
2101
|
+
}
|
|
2102
|
+
if (message.type === "dispose") {
|
|
2103
|
+
postResponse({
|
|
2104
|
+
type: "response",
|
|
2105
|
+
id: message.id,
|
|
2106
|
+
ok: true,
|
|
2107
|
+
result: true,
|
|
2108
|
+
});
|
|
2109
|
+
close();
|
|
2110
|
+
}
|
|
2111
|
+
}
|
|
2112
|
+
catch (err) {
|
|
2113
|
+
const error = err;
|
|
2114
|
+
postResponse({
|
|
2115
|
+
type: "response",
|
|
2116
|
+
id: message.id,
|
|
2117
|
+
ok: false,
|
|
2118
|
+
error: {
|
|
2119
|
+
message: error?.message ?? String(err),
|
|
2120
|
+
stack: error?.stack,
|
|
2121
|
+
code: error?.code,
|
|
2122
|
+
},
|
|
2123
|
+
});
|
|
2124
|
+
}
|
|
2125
|
+
};
|