@novolobos/nodevm 3.10.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,907 @@
1
+ /* global host, bridge, data, context */
2
+
3
+ 'use strict';
4
+
5
+ const {
6
+ Object: localObject,
7
+ Array: localArray,
8
+ Error: LocalError,
9
+ Reflect: localReflect,
10
+ Proxy: LocalProxy,
11
+ WeakMap: LocalWeakMap,
12
+ Function: localFunction,
13
+ eval: localEval
14
+ } = global;
15
+
16
+ const {
17
+ freeze: localObjectFreeze
18
+ } = localObject;
19
+
20
+ const {
21
+ getPrototypeOf: localReflectGetPrototypeOf,
22
+ apply,
23
+ construct: localReflectConstruct,
24
+ deleteProperty: localReflectDeleteProperty,
25
+ has: localReflectHas,
26
+ defineProperty: localReflectDefineProperty,
27
+ setPrototypeOf: localReflectSetPrototypeOf,
28
+ getOwnPropertyDescriptor: localReflectGetOwnPropertyDescriptor,
29
+ ownKeys: localReflectOwnKeys
30
+ } = localReflect;
31
+
32
+ const localObjectGetOwnPropertySymbols = localObject.getOwnPropertySymbols;
33
+ const localObjectGetOwnPropertyDescriptors = localObject.getOwnPropertyDescriptors;
34
+ const localObjectAssign = localObject.assign;
35
+
36
+ const speciesSymbol = Symbol.species;
37
+ const globalPromise = global.Promise;
38
+ class localPromise extends globalPromise {}
39
+
40
+ /*
41
+ * Symbol.for protection
42
+ *
43
+ * Certain Node.js cross-realm symbols can be exploited for sandbox escapes:
44
+ *
45
+ * - 'nodejs.util.inspect.custom': Called by util.inspect with host's inspect function as argument.
46
+ * If sandbox defines this on an object passed to host APIs (e.g., WebAssembly.compileStreaming),
47
+ * Node's error handling calls the custom function with host context, enabling escape.
48
+ *
49
+ * - 'nodejs.rejection': Called by EventEmitter on promise rejection with captureRejections enabled.
50
+ * The handler receives error objects that could potentially leak host context.
51
+ *
52
+ * Fix: Override Symbol.for to return sandbox-local symbols for dangerous keys instead of cross-realm
53
+ * symbols. This prevents Node.js internals from recognizing sandbox-defined symbol properties while
54
+ * preserving cross-realm behavior for other symbols.
55
+ */
56
+ const originalSymbolFor = Symbol.for;
57
+ const blockedSymbolCustomInspect = Symbol('nodejs.util.inspect.custom');
58
+ const blockedSymbolRejection = Symbol('nodejs.rejection');
59
+
60
+ Symbol.for = function(key) {
61
+ // Convert to string once to prevent toString/toPrimitive bypass and TOCTOU attacks
62
+ const keyStr = '' + key;
63
+ if (keyStr === 'nodejs.util.inspect.custom') {
64
+ return blockedSymbolCustomInspect;
65
+ }
66
+ if (keyStr === 'nodejs.rejection') {
67
+ return blockedSymbolRejection;
68
+ }
69
+ return originalSymbolFor(keyStr);
70
+ };
71
+
72
+ /*
73
+ * Cross-realm symbol extraction protection
74
+ *
75
+ * Even with Symbol.for overridden, cross-realm symbols can be extracted from
76
+ * host objects exposed to the sandbox (e.g., Buffer.prototype) via:
77
+ * Object.getOwnPropertySymbols(Buffer.prototype).find(s => s.description === 'nodejs.util.inspect.custom')
78
+ *
79
+ * Fix: Override Object.getOwnPropertySymbols and Reflect.ownKeys to replace
80
+ * dangerous cross-realm symbols with sandbox-local equivalents in results.
81
+ */
82
+ const realSymbolCustomInspect = originalSymbolFor('nodejs.util.inspect.custom');
83
+ const realSymbolRejection = originalSymbolFor('nodejs.rejection');
84
+
85
+ function isDangerousSymbol(sym) {
86
+ return sym === realSymbolCustomInspect || sym === realSymbolRejection;
87
+ }
88
+
89
+ localObject.getOwnPropertySymbols = function getOwnPropertySymbols(obj) {
90
+ const symbols = apply(localObjectGetOwnPropertySymbols, localObject, [obj]);
91
+ const result = [];
92
+ let j = 0;
93
+ for (let i = 0; i < symbols.length; i++) {
94
+ if (typeof symbols[i] !== 'symbol' || !isDangerousSymbol(symbols[i])) {
95
+ localReflectDefineProperty(result, j++, {
96
+ __proto__: null,
97
+ value: symbols[i],
98
+ writable: true,
99
+ enumerable: true,
100
+ configurable: true
101
+ });
102
+ }
103
+ }
104
+ return result;
105
+ };
106
+
107
+ localReflect.ownKeys = function ownKeys(obj) {
108
+ const keys = apply(localReflectOwnKeys, localReflect, [obj]);
109
+ const result = [];
110
+ let j = 0;
111
+ for (let i = 0; i < keys.length; i++) {
112
+ if (typeof keys[i] !== 'symbol' || !isDangerousSymbol(keys[i])) {
113
+ localReflectDefineProperty(result, j++, {
114
+ __proto__: null,
115
+ value: keys[i],
116
+ writable: true,
117
+ enumerable: true,
118
+ configurable: true
119
+ });
120
+ }
121
+ }
122
+ return result;
123
+ };
124
+
125
+ /*
126
+ * Object.getOwnPropertyDescriptors uses the internal [[OwnPropertyKeys]] which
127
+ * bypasses our Reflect.ownKeys override. The result object has dangerous symbols
128
+ * as property keys, which can then be leaked via Object.assign/Object.defineProperties
129
+ * to a Proxy whose set/defineProperty trap captures the key.
130
+ */
131
+ localObject.getOwnPropertyDescriptors = function getOwnPropertyDescriptors(obj) {
132
+ const descs = apply(localObjectGetOwnPropertyDescriptors, localObject, [obj]);
133
+ localReflectDeleteProperty(descs, realSymbolCustomInspect);
134
+ localReflectDeleteProperty(descs, realSymbolRejection);
135
+ return descs;
136
+ };
137
+
138
+ /*
139
+ * Object.assign uses internal [[OwnPropertyKeys]] on source objects, bypassing our
140
+ * Reflect.ownKeys override. If a source (bridge proxy) has an enumerable dangerous-symbol
141
+ * property, the symbol is passed to the target's [[Set]] which could be a user Proxy trap.
142
+ */
143
+ localObject.assign = function assign(target) {
144
+ if (target === null || target === undefined) {
145
+ throw new LocalError('Cannot convert undefined or null to object');
146
+ }
147
+ const to = localObject(target);
148
+ for (let s = 1; s < arguments.length; s++) {
149
+ const source = arguments[s];
150
+ if (source === null || source === undefined) continue;
151
+ const from = localObject(source);
152
+ const keys = apply(localReflectOwnKeys, localReflect, [from]);
153
+ for (let i = 0; i < keys.length; i++) {
154
+ const key = keys[i];
155
+ if (typeof key === 'symbol' && isDangerousSymbol(key)) continue;
156
+ const desc = apply(localReflectGetOwnPropertyDescriptor, localReflect, [from, key]);
157
+ if (desc && desc.enumerable === true) {
158
+ to[key] = from[key];
159
+ }
160
+ }
161
+ }
162
+ return to;
163
+ };
164
+
165
+ const resetPromiseSpecies = (p) => {
166
+ // Note: We do not use instanceof to check if p is a Promise because
167
+ // Reflect.construct(Promise, [...], FakeNewTarget) can create a real Promise
168
+ // (with internal slots) whose prototype does not include globalPromise.prototype,
169
+ // bypassing the instanceof check entirely.
170
+ //
171
+ // Instead, we unconditionally set the constructor property on any object.
172
+ // This ensures species resolution always uses localPromise, regardless of
173
+ // how the promise was constructed.
174
+ if (p !== null && (typeof p === 'object' || typeof p === 'function')) {
175
+ // Always define an own data property for 'constructor' to eliminate
176
+ // any TOCTOU vulnerability. Accessor properties (getters) on either the
177
+ // instance or anywhere in the prototype chain can return different values
178
+ // on each access, allowing an attacker to pass our check on the first read
179
+ // while V8 internally sees a malicious species on subsequent reads.
180
+ let success;
181
+ try {
182
+ success = localReflectDefineProperty(p, 'constructor', { __proto__: null, value: localPromise, writable: true, configurable: true });
183
+ } catch (e) {
184
+ // If defineProperty throws (e.g., Proxy with throwing trap), treat as failure
185
+ success = false;
186
+ }
187
+ if (!success) {
188
+ throw new LocalError('Unsafe Promise species cannot be reset');
189
+ }
190
+ }
191
+ };
192
+
193
+ const globalPromiseThen = globalPromise.prototype.then;
194
+ const globalPromiseCatch = globalPromise.prototype.catch;
195
+
196
+ globalPromise.prototype.then = function then(onFulfilled, onRejected) {
197
+ resetPromiseSpecies(this);
198
+ if (typeof onFulfilled === 'function') {
199
+ const origOnFulfilled = onFulfilled;
200
+ onFulfilled = function onFulfilled(value) {
201
+ value = ensureThis(value);
202
+ return apply(origOnFulfilled, this, [value]);
203
+ };
204
+ }
205
+ if (typeof onRejected === 'function') {
206
+ const origOnRejected = onRejected;
207
+ onRejected = function onRejected(error) {
208
+ error = handleException(error);
209
+ return apply(origOnRejected, this, [error]);
210
+ };
211
+ }
212
+ return apply(globalPromiseThen, this, [onFulfilled, onRejected]);
213
+ };
214
+
215
+ globalPromise.prototype.catch = function _catch(onRejected) {
216
+ resetPromiseSpecies(this);
217
+ if (typeof onRejected === 'function') {
218
+ const origOnRejected = onRejected;
219
+ onRejected = function onRejected(error) {
220
+ error = handleException(error);
221
+ return apply(origOnRejected, this, [error]);
222
+ };
223
+ }
224
+ return apply(globalPromiseCatch, this, [onRejected]);
225
+ };
226
+
227
+ const localReflectApply = (target, thisArg, args) => {
228
+ resetPromiseSpecies(thisArg);
229
+ return apply(target, thisArg, args);
230
+ };
231
+
232
+ const {
233
+ isArray: localArrayIsArray
234
+ } = localArray;
235
+
236
+ const {
237
+ ensureThis,
238
+ ReadOnlyHandler,
239
+ from,
240
+ fromWithFactory,
241
+ readonlyFactory,
242
+ connect,
243
+ addProtoMapping,
244
+ VMError,
245
+ ReadOnlyMockHandler
246
+ } = bridge;
247
+
248
+ const {
249
+ allowAsync,
250
+ GeneratorFunction,
251
+ AsyncFunction,
252
+ AsyncGeneratorFunction
253
+ } = data;
254
+
255
+ const {
256
+ get: localWeakMapGet,
257
+ set: localWeakMapSet
258
+ } = LocalWeakMap.prototype;
259
+
260
+ function localUnexpected() {
261
+ return new VMError('Should not happen');
262
+ }
263
+
264
+ // global is originally prototype of host.Object so it can be used to climb up from the sandbox.
265
+ if (!localReflectSetPrototypeOf(context, localObject.prototype)) throw localUnexpected();
266
+
267
+ Object.defineProperties(global, {
268
+ global: {value: global, writable: true, configurable: true, enumerable: true},
269
+ globalThis: {value: global, writable: true, configurable: true},
270
+ GLOBAL: {value: global, writable: true, configurable: true},
271
+ root: {value: global, writable: true, configurable: true},
272
+ Error: {value: LocalError},
273
+ Promise: {value: localPromise},
274
+ Proxy: {value: undefined}
275
+ });
276
+
277
+ /*
278
+ * WebAssembly.JSTag protection
279
+ *
280
+ * WebAssembly.JSTag (Node 25+) allows wasm exception handling to catch JavaScript
281
+ * exceptions via try_table/catch with JSTag. This completely bypasses the transformer's
282
+ * catch block instrumentation, which only wraps JavaScript catch clauses with
283
+ * handleException(). An attacker can:
284
+ * 1. Create a wasm module that imports JSTag and catches JS exceptions
285
+ * 2. Import a function that triggers a host TypeError (e.g., via Symbol() name trick)
286
+ * 3. Catch the host error in wasm, returning it as an externref
287
+ * 4. Use the raw host error's constructor chain to escape
288
+ *
289
+ * Fix: Remove WebAssembly.JSTag from the sandbox. Without it, wasm code cannot
290
+ * catch JavaScript exceptions — catch_all provides no value access, and catch_all_ref
291
+ * requires JSTag for exn.extract. The tag is a V8 internal and cannot be reconstructed.
292
+ */
293
+ if (typeof WebAssembly !== 'undefined' && WebAssembly.JSTag !== undefined) {
294
+ localReflectDeleteProperty(WebAssembly, 'JSTag');
295
+ }
296
+
297
+ if (!localReflectDefineProperty(global, 'VMError', {
298
+ __proto__: null,
299
+ value: VMError,
300
+ writable: true,
301
+ enumerable: false,
302
+ configurable: true
303
+ })) throw localUnexpected();
304
+
305
+ // Fixes buffer unsafe allocation
306
+ /* eslint-disable no-use-before-define */
307
+ class BufferHandler extends ReadOnlyHandler {
308
+
309
+ apply(target, thiz, args) {
310
+ if (args.length > 0 && typeof args[0] === 'number') {
311
+ return LocalBuffer.alloc(args[0]);
312
+ }
313
+ return localReflectApply(LocalBuffer.from, LocalBuffer, args);
314
+ }
315
+
316
+ construct(target, args, newTarget) {
317
+ if (args.length > 0 && typeof args[0] === 'number') {
318
+ return LocalBuffer.alloc(args[0]);
319
+ }
320
+ return localReflectApply(LocalBuffer.from, LocalBuffer, args);
321
+ }
322
+
323
+ }
324
+ /* eslint-enable no-use-before-define */
325
+
326
+ const LocalBuffer = fromWithFactory(obj => new BufferHandler(obj), host.Buffer);
327
+
328
+
329
+ if (!localReflectDefineProperty(global, 'Buffer', {
330
+ __proto__: null,
331
+ value: LocalBuffer,
332
+ writable: true,
333
+ enumerable: false,
334
+ configurable: true
335
+ })) throw localUnexpected();
336
+
337
+ addProtoMapping(LocalBuffer.prototype, host.Buffer.prototype, 'Uint8Array');
338
+
339
+ /**
340
+ *
341
+ * @param {*} size Size of new buffer
342
+ * @this LocalBuffer
343
+ * @return {LocalBuffer}
344
+ */
345
+ function allocUnsafe(size) {
346
+ return LocalBuffer.alloc(size);
347
+ }
348
+
349
+ connect(allocUnsafe, host.Buffer.allocUnsafe);
350
+
351
+ /**
352
+ *
353
+ * @param {*} size Size of new buffer
354
+ * @this LocalBuffer
355
+ * @return {LocalBuffer}
356
+ */
357
+ function allocUnsafeSlow(size) {
358
+ return LocalBuffer.alloc(size);
359
+ }
360
+
361
+ connect(allocUnsafeSlow, host.Buffer.allocUnsafeSlow);
362
+
363
+ /**
364
+ * Replacement for Buffer inspect
365
+ *
366
+ * @param {*} recurseTimes
367
+ * @param {*} ctx
368
+ * @this LocalBuffer
369
+ * @return {string}
370
+ */
371
+ function inspect(recurseTimes, ctx) {
372
+ // Mimic old behavior, could throw but didn't pass a test.
373
+ const max = host.INSPECT_MAX_BYTES;
374
+ const actualMax = Math.min(max, this.length);
375
+ const remaining = this.length - max;
376
+ let str = this.hexSlice(0, actualMax).replace(/(.{2})/g, '$1 ').trim();
377
+ if (remaining > 0) str += ` ... ${remaining} more byte${remaining > 1 ? 's' : ''}`;
378
+ return `<${this.constructor.name} ${str}>`;
379
+ }
380
+
381
+ connect(inspect, host.Buffer.prototype.inspect);
382
+
383
+ connect(localFunction.prototype.bind, host.Function.prototype.bind);
384
+
385
+ connect(localObject.prototype.__defineGetter__, host.Object.prototype.__defineGetter__);
386
+ connect(localObject.prototype.__defineSetter__, host.Object.prototype.__defineSetter__);
387
+ connect(localObject.prototype.__lookupGetter__, host.Object.prototype.__lookupGetter__);
388
+ connect(localObject.prototype.__lookupSetter__, host.Object.prototype.__lookupSetter__);
389
+
390
+ /*
391
+ * PrepareStackTrace sanitization
392
+ */
393
+
394
+ const oldPrepareStackTraceDesc = localReflectGetOwnPropertyDescriptor(LocalError, 'prepareStackTrace');
395
+
396
+ let currentPrepareStackTrace = LocalError.prepareStackTrace;
397
+ const wrappedPrepareStackTrace = new LocalWeakMap();
398
+ if (typeof currentPrepareStackTrace === 'function') {
399
+ wrappedPrepareStackTrace.set(currentPrepareStackTrace, currentPrepareStackTrace);
400
+ }
401
+
402
+ let OriginalCallSite;
403
+ LocalError.prepareStackTrace = (e, sst) => {
404
+ OriginalCallSite = sst[0].constructor;
405
+ };
406
+ new LocalError().stack;
407
+ if (typeof OriginalCallSite === 'function') {
408
+ LocalError.prepareStackTrace = undefined;
409
+
410
+ function makeCallSiteGetters(list) {
411
+ const callSiteGetters = [];
412
+ for (let i=0; i<list.length; i++) {
413
+ const name = list[i];
414
+ const func = OriginalCallSite.prototype[name];
415
+ callSiteGetters[i] = {__proto__: null,
416
+ name,
417
+ propName: '_' + name,
418
+ func: (thiz) => {
419
+ return localReflectApply(func, thiz, []);
420
+ }
421
+ };
422
+ }
423
+ return callSiteGetters;
424
+ }
425
+
426
+ function applyCallSiteGetters(thiz, callSite, getters) {
427
+ for (let i=0; i<getters.length; i++) {
428
+ const getter = getters[i];
429
+ localReflectDefineProperty(thiz, getter.propName, {
430
+ __proto__: null,
431
+ value: getter.func(callSite)
432
+ });
433
+ }
434
+ }
435
+
436
+ const callSiteGetters = makeCallSiteGetters([
437
+ 'getTypeName',
438
+ 'getFunctionName',
439
+ 'getMethodName',
440
+ 'getFileName',
441
+ 'getLineNumber',
442
+ 'getColumnNumber',
443
+ 'getEvalOrigin',
444
+ 'isToplevel',
445
+ 'isEval',
446
+ 'isNative',
447
+ 'isConstructor',
448
+ 'isAsync',
449
+ 'isPromiseAll',
450
+ 'getPromiseIndex'
451
+ ]);
452
+
453
+ class CallSite {
454
+ constructor(callSite) {
455
+ applyCallSiteGetters(this, callSite, callSiteGetters);
456
+ }
457
+ getThis() {
458
+ return undefined;
459
+ }
460
+ getFunction() {
461
+ return undefined;
462
+ }
463
+ toString() {
464
+ return 'CallSite {}';
465
+ }
466
+ }
467
+
468
+
469
+ for (let i=0; i<callSiteGetters.length; i++) {
470
+ const name = callSiteGetters[i].name;
471
+ const funcProp = localReflectGetOwnPropertyDescriptor(OriginalCallSite.prototype, name);
472
+ if (!funcProp) continue;
473
+ const propertyName = callSiteGetters[i].propName;
474
+ const func = {func() {
475
+ return this[propertyName];
476
+ }}.func;
477
+ const nameProp = localReflectGetOwnPropertyDescriptor(func, 'name');
478
+ if (!nameProp) throw localUnexpected();
479
+ nameProp.value = name;
480
+ if (!localReflectDefineProperty(func, 'name', nameProp)) throw localUnexpected();
481
+ funcProp.value = func;
482
+ if (!localReflectDefineProperty(CallSite.prototype, name, funcProp)) throw localUnexpected();
483
+ }
484
+
485
+ if (!localReflectDefineProperty(LocalError, 'prepareStackTrace', {
486
+ configurable: false,
487
+ enumerable: false,
488
+ get() {
489
+ return currentPrepareStackTrace;
490
+ },
491
+ set(value) {
492
+ if (typeof(value) !== 'function') {
493
+ currentPrepareStackTrace = value;
494
+ return;
495
+ }
496
+ const wrapped = localReflectApply(localWeakMapGet, wrappedPrepareStackTrace, [value]);
497
+ if (wrapped) {
498
+ currentPrepareStackTrace = wrapped;
499
+ return;
500
+ }
501
+ const newWrapped = (error, sst) => {
502
+ const sandboxSst = ensureThis(sst);
503
+ if (localArrayIsArray(sst)) {
504
+ if (sst === sandboxSst) {
505
+ for (let i=0; i < sst.length; i++) {
506
+ const cs = sst[i];
507
+ if (typeof cs === 'object' && localReflectGetPrototypeOf(cs) === OriginalCallSite.prototype) {
508
+ sst[i] = new CallSite(cs);
509
+ }
510
+ }
511
+ } else {
512
+ sst = [];
513
+ for (let i=0; i < sandboxSst.length; i++) {
514
+ const cs = sandboxSst[i];
515
+ localReflectDefineProperty(sst, i, {
516
+ __proto__: null,
517
+ value: new CallSite(cs),
518
+ enumerable: true,
519
+ configurable: true,
520
+ writable: true
521
+ });
522
+ }
523
+ }
524
+ } else {
525
+ sst = sandboxSst;
526
+ }
527
+ return value(error, sst);
528
+ };
529
+ localReflectApply(localWeakMapSet, wrappedPrepareStackTrace, [value, newWrapped]);
530
+ localReflectApply(localWeakMapSet, wrappedPrepareStackTrace, [newWrapped, newWrapped]);
531
+ currentPrepareStackTrace = newWrapped;
532
+ }
533
+ })) throw localUnexpected();
534
+ } else if (oldPrepareStackTraceDesc) {
535
+ localReflectDefineProperty(LocalError, 'prepareStackTrace', oldPrepareStackTraceDesc);
536
+ } else {
537
+ localReflectDeleteProperty(LocalError, 'prepareStackTrace');
538
+ }
539
+
540
+ /*
541
+ * Exception sanitization
542
+ */
543
+
544
+ /*
545
+ * SuppressedError sanitization
546
+ *
547
+ * When V8 internally creates SuppressedError during DisposableStack.dispose()
548
+ * or 'using' declarations, the .error and .suppressed properties may contain
549
+ * host-realm errors (e.g., TypeError from Symbol() name trick). Since the
550
+ * SuppressedError is created in the sandbox context, ensureThis returns it
551
+ * as-is, leaving its sub-error properties unsanitized.
552
+ *
553
+ * Fix: handleException detects SuppressedError instances and recursively
554
+ * sanitizes their .error and .suppressed properties via ensureThis.
555
+ */
556
+ const localSuppressedErrorProto = (typeof SuppressedError === 'function') ? SuppressedError.prototype : null;
557
+
558
+ function handleException(e, visited) {
559
+ e = ensureThis(e);
560
+ if (localSuppressedErrorProto !== null && e !== null && typeof e === 'object') {
561
+ if (!visited) visited = new LocalWeakMap();
562
+ // Cycle detection: if we've already visited this object, stop recursing
563
+ if (apply(localWeakMapGet, visited, [e])) return e;
564
+ apply(localWeakMapSet, visited, [e, true]);
565
+ let proto;
566
+ try {
567
+ proto = localReflectGetPrototypeOf(e);
568
+ } catch (ex) {
569
+ return e;
570
+ }
571
+ while (proto !== null) {
572
+ if (proto === localSuppressedErrorProto) {
573
+ e.error = handleException(e.error, visited);
574
+ e.suppressed = handleException(e.suppressed, visited);
575
+ return e;
576
+ }
577
+ try {
578
+ proto = localReflectGetPrototypeOf(proto);
579
+ } catch (ex) {
580
+ return e;
581
+ }
582
+ }
583
+ }
584
+ return e;
585
+ }
586
+
587
+ const withProxy = localObjectFreeze({
588
+ __proto__: null,
589
+ has(target, key) {
590
+ if (key === host.INTERNAL_STATE_NAME) return false;
591
+ return localReflectHas(target, key);
592
+ }
593
+ });
594
+
595
+ const interanState = localObjectFreeze({
596
+ __proto__: null,
597
+ wrapWith(x) {
598
+ if (x === null || x === undefined) return x;
599
+ return new LocalProxy(localObject(x), withProxy);
600
+ },
601
+ handleException,
602
+ import(what) {
603
+ throw new VMError('Dynamic Import not supported');
604
+ }
605
+ });
606
+
607
+ if (!localReflectDefineProperty(global, host.INTERNAL_STATE_NAME, {
608
+ __proto__: null,
609
+ configurable: false,
610
+ enumerable: false,
611
+ writable: false,
612
+ value: interanState
613
+ })) throw localUnexpected();
614
+
615
+ /*
616
+ * Eval sanitization
617
+ */
618
+
619
+ function throwAsync() {
620
+ return new VMError('Async not available');
621
+ }
622
+
623
+ function makeFunction(inputArgs, isAsync, isGenerator) {
624
+ const lastArgs = inputArgs.length - 1;
625
+ let code = lastArgs >= 0 ? `${inputArgs[lastArgs]}` : '';
626
+ let args = lastArgs > 0 ? `${inputArgs[0]}` : '';
627
+ for (let i = 1; i < lastArgs; i++) {
628
+ args += `,${inputArgs[i]}`;
629
+ }
630
+ try {
631
+ code = host.transformAndCheck(args, code, isAsync, isGenerator, allowAsync);
632
+ } catch (e) {
633
+ throw bridge.from(e);
634
+ }
635
+ return localEval(code);
636
+ }
637
+
638
+ const FunctionHandler = {
639
+ __proto__: null,
640
+ apply(target, thiz, args) {
641
+ return makeFunction(args, this.isAsync, this.isGenerator);
642
+ },
643
+ construct(target, args, newTarget) {
644
+ return makeFunction(args, this.isAsync, this.isGenerator);
645
+ }
646
+ };
647
+
648
+ const EvalHandler = {
649
+ __proto__: null,
650
+ apply(target, thiz, args) {
651
+ if (args.length === 0) return undefined;
652
+ let code = `${args[0]}`;
653
+ try {
654
+ code = host.transformAndCheck(null, code, false, false, allowAsync);
655
+ } catch (e) {
656
+ throw bridge.from(e);
657
+ }
658
+ return localEval(code);
659
+ }
660
+ };
661
+
662
+ const AsyncErrorHandler = {
663
+ __proto__: null,
664
+ apply(target, thiz, args) {
665
+ throw throwAsync();
666
+ },
667
+ construct(target, args, newTarget) {
668
+ throw throwAsync();
669
+ }
670
+ };
671
+
672
+ function makeCheckFunction(isAsync, isGenerator) {
673
+ if (isAsync && !allowAsync) return AsyncErrorHandler;
674
+ return {
675
+ __proto__: FunctionHandler,
676
+ isAsync,
677
+ isGenerator
678
+ };
679
+ }
680
+
681
+ function overrideWithProxy(obj, prop, value, handler) {
682
+ const proxy = new LocalProxy(value, handler);
683
+ if (!localReflectDefineProperty(obj, prop, {__proto__: null, value: proxy})) throw localUnexpected();
684
+ return proxy;
685
+ }
686
+
687
+ const proxiedFunction = overrideWithProxy(localFunction.prototype, 'constructor', localFunction, makeCheckFunction(false, false));
688
+ if (GeneratorFunction) {
689
+ if (!localReflectSetPrototypeOf(GeneratorFunction, proxiedFunction)) throw localUnexpected();
690
+ overrideWithProxy(GeneratorFunction.prototype, 'constructor', GeneratorFunction, makeCheckFunction(false, true));
691
+ }
692
+ if (AsyncFunction) {
693
+ if (!localReflectSetPrototypeOf(AsyncFunction, proxiedFunction)) throw localUnexpected();
694
+ overrideWithProxy(AsyncFunction.prototype, 'constructor', AsyncFunction, makeCheckFunction(true, false));
695
+ }
696
+ if (AsyncGeneratorFunction) {
697
+ if (!localReflectSetPrototypeOf(AsyncGeneratorFunction, proxiedFunction)) throw localUnexpected();
698
+ overrideWithProxy(AsyncGeneratorFunction.prototype, 'constructor', AsyncGeneratorFunction, makeCheckFunction(true, true));
699
+ }
700
+
701
+ function makeSafeHandlerArgs(args) {
702
+ const sArgs = ensureThis(args);
703
+ if (sArgs === args) return args;
704
+ const a = [];
705
+ for (let i=0; i < sArgs.length; i++) {
706
+ localReflectDefineProperty(a, i, {
707
+ __proto__: null,
708
+ value: sArgs[i],
709
+ enumerable: true,
710
+ configurable: true,
711
+ writable: true
712
+ });
713
+ }
714
+ return a;
715
+ }
716
+
717
+ const makeSafeArgs = Object.freeze({
718
+ __proto__: null,
719
+ apply(target, thiz, args) {
720
+ return localReflectApply(target, thiz, makeSafeHandlerArgs(args));
721
+ },
722
+ construct(target, args, newTarget) {
723
+ return localReflectConstruct(target, makeSafeHandlerArgs(args), newTarget);
724
+ }
725
+ });
726
+
727
+ const proxyHandlerHandler = Object.freeze({
728
+ __proto__: null,
729
+ get(target, name, receiver) {
730
+ if (name === 'isProxy') return true;
731
+ const value = target.handler[name];
732
+ if (typeof value !== 'function') return value;
733
+ return new LocalProxy(value, makeSafeArgs);
734
+ }
735
+ });
736
+
737
+ function wrapProxyHandler(args) {
738
+ if (args.length < 2) return args;
739
+ const handler = args[1];
740
+ args[1] = new LocalProxy({__proto__: null, handler}, proxyHandlerHandler);
741
+ return args;
742
+ }
743
+
744
+ const proxyHandler = Object.freeze({
745
+ __proto__: null,
746
+ apply(target, thiz, args) {
747
+ return localReflectApply(target, thiz, wrapProxyHandler(args));
748
+ },
749
+ construct(target, args, newTarget) {
750
+ return localReflectConstruct(target, wrapProxyHandler(args), newTarget);
751
+ }
752
+ });
753
+
754
+ const proxiedProxy = new LocalProxy(LocalProxy, proxyHandler);
755
+
756
+ overrideWithProxy(LocalProxy, 'revocable', LocalProxy.revocable, proxyHandler);
757
+
758
+ global.Proxy = proxiedProxy;
759
+ global.Function = proxiedFunction;
760
+ global.eval = new LocalProxy(localEval, EvalHandler);
761
+
762
+ /*
763
+ * Promise sanitization
764
+ */
765
+
766
+ if (localPromise) {
767
+
768
+ const PromisePrototype = localPromise.prototype;
769
+
770
+ if (!allowAsync) {
771
+
772
+ overrideWithProxy(PromisePrototype, 'then', PromisePrototype.then, AsyncErrorHandler);
773
+ // This seems not to work, and will produce
774
+ // UnhandledPromiseRejectionWarning: TypeError: Method Promise.prototype.then called on incompatible receiver [object Object].
775
+ // This is likely caused since the host.Promise.prototype.then cannot use the VM Proxy object.
776
+ // Contextify.connect(host.Promise.prototype.then, Promise.prototype.then);
777
+
778
+ } else {
779
+
780
+ overrideWithProxy(PromisePrototype, 'then', PromisePrototype.then, {
781
+ __proto__: null,
782
+ apply(target, thiz, args) {
783
+ if (args.length > 0) {
784
+ const onFulfilled = args[0];
785
+ if (typeof onFulfilled === 'function') {
786
+ args[0] = function sanitizedOnFulfilled(value) {
787
+ value = ensureThis(value);
788
+ return localReflectApply(onFulfilled, this, [value]);
789
+ };
790
+ }
791
+ }
792
+ if (args.length > 1) {
793
+ const onRejected = args[1];
794
+ if (typeof onRejected === 'function') {
795
+ args[1] = function sanitizedOnRejected(error) {
796
+ error = handleException(error);
797
+ return localReflectApply(onRejected, this, [error]);
798
+ };
799
+ }
800
+ }
801
+ return localReflectApply(target, thiz, args);
802
+ }
803
+ });
804
+
805
+ overrideWithProxy(PromisePrototype, 'catch', PromisePrototype.catch, {
806
+ __proto__: null,
807
+ apply(target, thiz, args) {
808
+ if (args.length > 0) {
809
+ const onRejected = args[0];
810
+ if (typeof onRejected === 'function') {
811
+ args[0] = function sanitizedOnRejected(error) {
812
+ error = handleException(error);
813
+ return localReflectApply(onRejected, this, [error]);
814
+ };
815
+ }
816
+ }
817
+ return localReflectApply(target, thiz, args);
818
+ }
819
+ });
820
+
821
+ }
822
+
823
+ // Secure Promise static methods to prevent species attacks via static method stealing.
824
+ //
825
+ // Several methods are vulnerable because they catch errors during iteration/resolution
826
+ // and pass them directly to the result promise's reject handler. If the attacker does:
827
+ // FakePromise.all = Promise.all; FakePromise.all(iterable);
828
+ // Then `this` inside Promise.all is FakePromise, so it creates the result promise using
829
+ // `new FakePromise(executor)`. When iteration throws a host error (e.g., from accessing
830
+ // error.stack with error.name = Symbol()), Promise.all catches it and passes it to
831
+ // FakePromise's reject handler, which receives the unsanitized host error.
832
+ //
833
+ // The fix wraps ALL Promise static methods to always use localPromise as the constructor,
834
+ // ignoring `this`. This provides defense in depth even for methods like reject/withResolvers
835
+ // that aren't currently known to be exploitable.
836
+ //
837
+ const globalPromiseTry = globalPromise.try;
838
+ if (typeof globalPromiseTry === 'function') {
839
+ globalPromise.try = function _try() {
840
+ return apply(globalPromiseTry, localPromise, arguments);
841
+ };
842
+ }
843
+
844
+ const globalPromiseAll = globalPromise.all;
845
+ globalPromise.all = function all(iterable) {
846
+ return apply(globalPromiseAll, localPromise, [iterable]);
847
+ };
848
+
849
+ const globalPromiseRace = globalPromise.race;
850
+ globalPromise.race = function race(iterable) {
851
+ return apply(globalPromiseRace, localPromise, [iterable]);
852
+ };
853
+
854
+ const globalPromiseAllSettled = globalPromise.allSettled;
855
+ if (typeof globalPromiseAllSettled === 'function') {
856
+ globalPromise.allSettled = function allSettled(iterable) {
857
+ return apply(globalPromiseAllSettled, localPromise, [iterable]);
858
+ };
859
+ }
860
+
861
+ const globalPromiseAny = globalPromise.any;
862
+ if (typeof globalPromiseAny === 'function') {
863
+ globalPromise.any = function any(iterable) {
864
+ return apply(globalPromiseAny, localPromise, [iterable]);
865
+ };
866
+ }
867
+
868
+ const globalPromiseResolve = globalPromise.resolve;
869
+ globalPromise.resolve = function resolve(value) {
870
+ return apply(globalPromiseResolve, localPromise, [value]);
871
+ };
872
+
873
+ const globalPromiseReject = globalPromise.reject;
874
+ globalPromise.reject = function reject(reason) {
875
+ return apply(globalPromiseReject, localPromise, [reason]);
876
+ };
877
+
878
+ const globalPromiseWithResolvers = globalPromise.withResolvers;
879
+ if (typeof globalPromiseWithResolvers === 'function') {
880
+ globalPromise.withResolvers = function withResolvers() {
881
+ return apply(globalPromiseWithResolvers, localPromise, []);
882
+ };
883
+ }
884
+
885
+ // Freeze globalPromise to prevent Symbol.hasInstance override
886
+ // (which would bypass the instanceof check in resetPromiseSpecies).
887
+ // Freeze globalPromise.prototype to prevent defining accessor properties
888
+ // on 'constructor' that could be used for TOCTOU attacks via the prototype chain.
889
+ Object.freeze(globalPromise);
890
+ Object.freeze(globalPromise.prototype);
891
+ Object.freeze(localPromise);
892
+ Object.freeze(PromisePrototype);
893
+ }
894
+
895
+
896
+ function readonly(other, mock) {
897
+ // Note: other@other(unsafe) mock@other(unsafe) returns@this(unsafe) throws@this(unsafe)
898
+ if (!mock) return fromWithFactory(readonlyFactory, other);
899
+ const tmock = from(mock);
900
+ return fromWithFactory(obj=>new ReadOnlyMockHandler(obj, tmock), other);
901
+ }
902
+
903
+ return {
904
+ __proto__: null,
905
+ readonly,
906
+ global
907
+ };