@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.
- package/LICENSE.md +9 -0
- package/README.md +461 -0
- package/bin/vm2 +3 -0
- package/index.d.ts +318 -0
- package/index.js +3 -0
- package/lib/bridge.js +1240 -0
- package/lib/builtin.js +147 -0
- package/lib/cli.js +35 -0
- package/lib/compiler.js +117 -0
- package/lib/events.js +977 -0
- package/lib/filesystem.js +84 -0
- package/lib/main.js +31 -0
- package/lib/nodevm.js +582 -0
- package/lib/resolver-compat.js +237 -0
- package/lib/resolver.js +882 -0
- package/lib/script.js +394 -0
- package/lib/setup-node-sandbox.js +461 -0
- package/lib/setup-sandbox.js +907 -0
- package/lib/transformer.js +198 -0
- package/lib/vm.js +545 -0
- package/package.json +48 -0
|
@@ -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
|
+
};
|