@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/lib/vm.js ADDED
@@ -0,0 +1,545 @@
1
+ 'use strict';
2
+
3
+ /**
4
+ * This callback will be called to transform a script to JavaScript.
5
+ *
6
+ * @callback compileCallback
7
+ * @param {string} code - Script code to transform to JavaScript.
8
+ * @param {string} filename - Filename of this script.
9
+ * @return {string} JavaScript code that represents the script code.
10
+ */
11
+
12
+ /**
13
+ * This callback will be called to resolve a module if it couldn't be found.
14
+ *
15
+ * @callback resolveCallback
16
+ * @param {string} moduleName - Name of the modulusedRequiree to resolve.
17
+ * @param {string} dirname - Name of the current directory.
18
+ * @return {(string|undefined)} The file or directory to use to load the requested module.
19
+ */
20
+
21
+ const fs = require('fs');
22
+ const pa = require('path');
23
+ const {
24
+ Script,
25
+ createContext
26
+ } = require('vm');
27
+ const {
28
+ EventEmitter
29
+ } = require('events');
30
+ const {
31
+ INSPECT_MAX_BYTES
32
+ } = require('buffer');
33
+ const {
34
+ createBridge,
35
+ VMError
36
+ } = require('./bridge');
37
+ const {
38
+ transformer,
39
+ INTERNAL_STATE_NAME
40
+ } = require('./transformer');
41
+ const {
42
+ lookupCompiler
43
+ } = require('./compiler');
44
+ const {
45
+ VMScript
46
+ } = require('./script');
47
+ const {
48
+ inspect
49
+ } = require('util');
50
+
51
+ const objectDefineProperties = Object.defineProperties;
52
+
53
+ /**
54
+ * Host objects
55
+ *
56
+ * @private
57
+ */
58
+ const HOST = Object.freeze({
59
+ Buffer,
60
+ Function,
61
+ Object,
62
+ transformAndCheck,
63
+ INSPECT_MAX_BYTES,
64
+ INTERNAL_STATE_NAME
65
+ });
66
+
67
+ /**
68
+ * Compile a script.
69
+ *
70
+ * @private
71
+ * @param {string} filename - Filename of the script.
72
+ * @param {string} script - Script.
73
+ * @return {vm.Script} The compiled script.
74
+ */
75
+ function compileScript(filename, script) {
76
+ return new Script(script, {
77
+ __proto__: null,
78
+ filename,
79
+ displayErrors: false
80
+ });
81
+ }
82
+
83
+ /**
84
+ * Default run options for vm.Script.runInContext
85
+ *
86
+ * @private
87
+ */
88
+ const DEFAULT_RUN_OPTIONS = Object.freeze({__proto__: null, displayErrors: false});
89
+
90
+ function checkAsync(allow) {
91
+ if (!allow) throw new VMError('Async not available');
92
+ }
93
+
94
+ function transformAndCheck(args, code, isAsync, isGenerator, allowAsync) {
95
+ const ret = transformer(args, code, isAsync, isGenerator, undefined);
96
+ checkAsync(allowAsync || !ret.hasAsync);
97
+ return ret.code;
98
+ }
99
+
100
+ /**
101
+ *
102
+ * This callback will be called and has a specific time to finish.<br>
103
+ * No parameters will be supplied.<br>
104
+ * If parameters are required, use a closure.
105
+ *
106
+ * @private
107
+ * @callback runWithTimeout
108
+ * @return {*}
109
+ *
110
+ */
111
+
112
+ let cacheTimeoutContext = null;
113
+ let cacheTimeoutScript = null;
114
+
115
+ /**
116
+ * Run a function with a specific timeout.
117
+ *
118
+ * @private
119
+ * @param {runWithTimeout} fn - Function to run with the specific timeout.
120
+ * @param {number} timeout - The amount of time to give the function to finish.
121
+ * @return {*} The value returned by the function.
122
+ * @throws {Error} If the function took to long.
123
+ */
124
+ function doWithTimeout(fn, timeout) {
125
+ if (!cacheTimeoutContext) {
126
+ cacheTimeoutContext = createContext();
127
+ cacheTimeoutScript = new Script('fn()', {
128
+ __proto__: null,
129
+ filename: 'timeout_bridge.js',
130
+ displayErrors: false
131
+ });
132
+ }
133
+ cacheTimeoutContext.fn = fn;
134
+ try {
135
+ return cacheTimeoutScript.runInContext(cacheTimeoutContext, {
136
+ __proto__: null,
137
+ displayErrors: false,
138
+ timeout
139
+ });
140
+ } finally {
141
+ cacheTimeoutContext.fn = null;
142
+ }
143
+ }
144
+
145
+ const bridgeScript = compileScript(`${__dirname}/bridge.js`,
146
+ `(function(global) {"use strict"; const exports = {};${fs.readFileSync(`${__dirname}/bridge.js`, 'utf8')}\nreturn exports;})`);
147
+ const setupSandboxScript = compileScript(`${__dirname}/setup-sandbox.js`,
148
+ `(function(global, host, bridge, data, context) { ${fs.readFileSync(`${__dirname}/setup-sandbox.js`, 'utf8')}\n})`);
149
+ const getGlobalScript = compileScript('get_global.js', 'this');
150
+
151
+ let getGeneratorFunctionScript = null;
152
+ let getAsyncFunctionScript = null;
153
+ let getAsyncGeneratorFunctionScript = null;
154
+ try {
155
+ getGeneratorFunctionScript = compileScript('get_generator_function.js', '(function*(){}).constructor');
156
+ } catch (ex) {}
157
+ try {
158
+ getAsyncFunctionScript = compileScript('get_async_function.js', '(async function(){}).constructor');
159
+ } catch (ex) {}
160
+ try {
161
+ getAsyncGeneratorFunctionScript = compileScript('get_async_generator_function.js', '(async function*(){}).constructor');
162
+ } catch (ex) {}
163
+
164
+ /**
165
+ * Class VM.
166
+ *
167
+ * @public
168
+ */
169
+ class VM extends EventEmitter {
170
+
171
+ /**
172
+ * The timeout for {@link VM#run} calls.
173
+ *
174
+ * @public
175
+ * @since v3.9.0
176
+ * @member {number} timeout
177
+ * @memberOf VM#
178
+ */
179
+
180
+ /**
181
+ * Get the global sandbox object.
182
+ *
183
+ * @public
184
+ * @readonly
185
+ * @since v3.9.0
186
+ * @member {Object} sandbox
187
+ * @memberOf VM#
188
+ */
189
+
190
+ /**
191
+ * The compiler to use to get the JavaScript code.
192
+ *
193
+ * @public
194
+ * @readonly
195
+ * @since v3.9.0
196
+ * @member {(string|compileCallback)} compiler
197
+ * @memberOf VM#
198
+ */
199
+
200
+ /**
201
+ * The resolved compiler to use to get the JavaScript code.
202
+ *
203
+ * @private
204
+ * @readonly
205
+ * @member {compileCallback} _compiler
206
+ * @memberOf VM#
207
+ */
208
+
209
+ /**
210
+ * Create a new VM instance.
211
+ *
212
+ * @public
213
+ * @param {Object} [options] - VM options.
214
+ * @param {number} [options.timeout] - The amount of time until a call to {@link VM#run} will timeout.
215
+ * @param {Object} [options.sandbox] - Objects that will be copied into the global object of the sandbox.
216
+ * @param {(string|compileCallback)} [options.compiler="javascript"] - The compiler to use.
217
+ * @param {boolean} [options.eval=true] - Allow the dynamic evaluation of code via eval(code) or Function(code)().<br>
218
+ * Only available for node v10+.
219
+ * @param {boolean} [options.wasm=true] - Allow to run wasm code.<br>
220
+ * Only available for node v10+.
221
+ * @param {boolean} [options.allowAsync=true] - Allows for async functions.
222
+ * @throws {VMError} If the compiler is unknown.
223
+ */
224
+ constructor(options = {}) {
225
+ super();
226
+
227
+ // Read all options
228
+ const {
229
+ timeout,
230
+ sandbox,
231
+ compiler = 'javascript',
232
+ compilerOptions,
233
+ allowAsync: optAllowAsync = true
234
+ } = options;
235
+ const allowEval = options.eval !== false;
236
+ const allowWasm = options.wasm !== false;
237
+ const allowAsync = optAllowAsync && !options.fixAsync;
238
+
239
+ // Early error if sandbox is not an object.
240
+ if (sandbox && 'object' !== typeof sandbox) {
241
+ throw new VMError('Sandbox must be object.');
242
+ }
243
+
244
+ // Early error if compiler can't be found.
245
+ const resolvedCompiler = lookupCompiler(compiler, compilerOptions);
246
+
247
+ // Create a new context for this vm.
248
+ const _context = createContext(undefined, {
249
+ __proto__: null,
250
+ codeGeneration: {
251
+ __proto__: null,
252
+ strings: allowEval,
253
+ wasm: allowWasm
254
+ }
255
+ });
256
+
257
+ const sandboxGlobal = getGlobalScript.runInContext(_context, DEFAULT_RUN_OPTIONS);
258
+
259
+ // Initialize the sandbox bridge
260
+ const {
261
+ createBridge: sandboxCreateBridge
262
+ } = bridgeScript.runInContext(_context, DEFAULT_RUN_OPTIONS)(sandboxGlobal);
263
+
264
+ // Initialize the bridge
265
+ const bridge = createBridge(sandboxCreateBridge, () => {});
266
+
267
+ const data = {
268
+ __proto__: null,
269
+ allowAsync
270
+ };
271
+
272
+ if (getGeneratorFunctionScript) {
273
+ data.GeneratorFunction = getGeneratorFunctionScript.runInContext(_context, DEFAULT_RUN_OPTIONS);
274
+ }
275
+ if (getAsyncFunctionScript) {
276
+ data.AsyncFunction = getAsyncFunctionScript.runInContext(_context, DEFAULT_RUN_OPTIONS);
277
+ }
278
+ if (getAsyncGeneratorFunctionScript) {
279
+ data.AsyncGeneratorFunction = getAsyncGeneratorFunctionScript.runInContext(_context, DEFAULT_RUN_OPTIONS);
280
+ }
281
+
282
+ // Create the bridge between the host and the sandbox.
283
+ const internal = setupSandboxScript.runInContext(_context, DEFAULT_RUN_OPTIONS)(sandboxGlobal, HOST, bridge.other, data, _context);
284
+
285
+ const runScript = (script) => {
286
+ // This closure is intentional to hide _context and bridge since the allow to access the sandbox directly which is unsafe.
287
+ let ret;
288
+ try {
289
+ ret = script.runInContext(_context, DEFAULT_RUN_OPTIONS);
290
+ } catch (e) {
291
+ throw bridge.from(e);
292
+ }
293
+ return bridge.from(ret);
294
+ };
295
+
296
+ const makeReadonly = (value, mock) => {
297
+ try {
298
+ internal.readonly(value, mock);
299
+ } catch (e) {
300
+ throw bridge.from(e);
301
+ }
302
+ return value;
303
+ };
304
+
305
+ const makeProtected = (value) => {
306
+ const sandboxBridge = bridge.other;
307
+ try {
308
+ sandboxBridge.fromWithFactory(sandboxBridge.protectedFactory, value);
309
+ } catch (e) {
310
+ throw bridge.from(e);
311
+ }
312
+ return value;
313
+ };
314
+
315
+ const addProtoMapping = (hostProto, sandboxProto) => {
316
+ const sandboxBridge = bridge.other;
317
+ let otherProto;
318
+ try {
319
+ otherProto = sandboxBridge.from(sandboxProto);
320
+ sandboxBridge.addProtoMapping(otherProto, hostProto);
321
+ } catch (e) {
322
+ throw bridge.from(e);
323
+ }
324
+ bridge.addProtoMapping(hostProto, otherProto);
325
+ };
326
+
327
+ const addProtoMappingFactory = (hostProto, sandboxProtoFactory) => {
328
+ const sandboxBridge = bridge.other;
329
+ const factory = () => {
330
+ const proto = sandboxProtoFactory(this);
331
+ bridge.addProtoMapping(hostProto, proto);
332
+ return proto;
333
+ };
334
+ try {
335
+ const otherProtoFactory = sandboxBridge.from(factory);
336
+ sandboxBridge.addProtoMappingFactory(otherProtoFactory, hostProto);
337
+ } catch (e) {
338
+ throw bridge.from(e);
339
+ }
340
+ };
341
+
342
+ // Define the properties of this object.
343
+ // Use Object.defineProperties here to be able to
344
+ // hide and set properties read-only.
345
+ objectDefineProperties(this, {
346
+ __proto__: null,
347
+ timeout: {
348
+ __proto__: null,
349
+ value: timeout,
350
+ writable: true,
351
+ enumerable: true
352
+ },
353
+ compiler: {
354
+ __proto__: null,
355
+ value: compiler,
356
+ enumerable: true
357
+ },
358
+ sandbox: {
359
+ __proto__: null,
360
+ value: bridge.from(sandboxGlobal),
361
+ enumerable: true
362
+ },
363
+ _runScript: {__proto__: null, value: runScript},
364
+ _makeReadonly: {__proto__: null, value: makeReadonly},
365
+ _makeProtected: {__proto__: null, value: makeProtected},
366
+ _addProtoMapping: {__proto__: null, value: addProtoMapping},
367
+ _addProtoMappingFactory: {__proto__: null, value: addProtoMappingFactory},
368
+ _compiler: {__proto__: null, value: resolvedCompiler},
369
+ _allowAsync: {__proto__: null, value: allowAsync}
370
+ });
371
+
372
+ this.readonly(inspect);
373
+
374
+ // prepare global sandbox
375
+ if (sandbox) {
376
+ this.setGlobals(sandbox);
377
+ }
378
+ }
379
+
380
+ /**
381
+ * Adds all the values to the globals.
382
+ *
383
+ * @public
384
+ * @since v3.9.0
385
+ * @param {Object} values - All values that will be added to the globals.
386
+ * @return {this} This for chaining.
387
+ * @throws {*} If the setter of a global throws an exception it is propagated. And the remaining globals will not be written.
388
+ */
389
+ setGlobals(values) {
390
+ for (const name in values) {
391
+ if (Object.prototype.hasOwnProperty.call(values, name)) {
392
+ this.sandbox[name] = values[name];
393
+ }
394
+ }
395
+ return this;
396
+ }
397
+
398
+ /**
399
+ * Set a global value.
400
+ *
401
+ * @public
402
+ * @since v3.9.0
403
+ * @param {string} name - The name of the global.
404
+ * @param {*} value - The value of the global.
405
+ * @return {this} This for chaining.
406
+ * @throws {*} If the setter of the global throws an exception it is propagated.
407
+ */
408
+ setGlobal(name, value) {
409
+ this.sandbox[name] = value;
410
+ return this;
411
+ }
412
+
413
+ /**
414
+ * Get a global value.
415
+ *
416
+ * @public
417
+ * @since v3.9.0
418
+ * @param {string} name - The name of the global.
419
+ * @return {*} The value of the global.
420
+ * @throws {*} If the getter of the global throws an exception it is propagated.
421
+ */
422
+ getGlobal(name) {
423
+ return this.sandbox[name];
424
+ }
425
+
426
+ /**
427
+ * Freezes the object inside VM making it read-only. Not available for primitive values.
428
+ *
429
+ * @public
430
+ * @param {*} value - Object to freeze.
431
+ * @param {string} [globalName] - Whether to add the object to global.
432
+ * @return {*} Object to freeze.
433
+ * @throws {*} If the setter of the global throws an exception it is propagated.
434
+ */
435
+ freeze(value, globalName) {
436
+ this.readonly(value);
437
+ if (globalName) this.sandbox[globalName] = value;
438
+ return value;
439
+ }
440
+
441
+ /**
442
+ * Freezes the object inside VM making it read-only. Not available for primitive values.
443
+ *
444
+ * @public
445
+ * @param {*} value - Object to freeze.
446
+ * @param {*} [mock] - When the object does not have a property the mock is used before prototype lookup.
447
+ * @return {*} Object to freeze.
448
+ */
449
+ readonly(value, mock) {
450
+ return this._makeReadonly(value, mock);
451
+ }
452
+
453
+ /**
454
+ * Protects the object inside VM making impossible to set functions as it's properties. Not available for primitive values.
455
+ *
456
+ * @public
457
+ * @param {*} value - Object to protect.
458
+ * @param {string} [globalName] - Whether to add the object to global.
459
+ * @return {*} Object to protect.
460
+ * @throws {*} If the setter of the global throws an exception it is propagated.
461
+ */
462
+ protect(value, globalName) {
463
+ this._makeProtected(value);
464
+ if (globalName) this.sandbox[globalName] = value;
465
+ return value;
466
+ }
467
+
468
+ /**
469
+ * Run the code in VM.
470
+ *
471
+ * @public
472
+ * @param {(string|VMScript)} code - Code to run.
473
+ * @param {(string|Object)} [options] - Options map or filename.
474
+ * @param {string} [options.filename="vm.js"] - Filename that shows up in any stack traces produced from this script.<br>
475
+ * This is only used if code is a String.
476
+ * @return {*} Result of executed code.
477
+ * @throws {SyntaxError} If there is a syntax error in the script.
478
+ * @throws {Error} An error is thrown when the script took to long and there is a timeout.
479
+ * @throws {*} If the script execution terminated with an exception it is propagated.
480
+ */
481
+ run(code, options) {
482
+ let script;
483
+ let filename;
484
+
485
+ if (typeof options === 'object') {
486
+ filename = options.filename;
487
+ } else {
488
+ filename = options;
489
+ }
490
+
491
+ if (code instanceof VMScript) {
492
+ script = code._compileVM();
493
+ checkAsync(this._allowAsync || !code._hasAsync);
494
+ } else {
495
+ const useFileName = filename || 'vm.js';
496
+ let scriptCode = this._compiler(code, useFileName);
497
+ const ret = transformer(null, scriptCode, false, false, useFileName);
498
+ scriptCode = ret.code;
499
+ checkAsync(this._allowAsync || !ret.hasAsync);
500
+ // Compile the script here so that we don't need to create a instance of VMScript.
501
+ script = new Script(scriptCode, {
502
+ __proto__: null,
503
+ filename: useFileName,
504
+ displayErrors: false
505
+ });
506
+ }
507
+
508
+ if (!this.timeout) {
509
+ return this._runScript(script);
510
+ }
511
+
512
+ return doWithTimeout(() => {
513
+ return this._runScript(script);
514
+ }, this.timeout);
515
+ }
516
+
517
+ /**
518
+ * Run the code in VM.
519
+ *
520
+ * @public
521
+ * @since v3.9.0
522
+ * @param {string} filename - Filename of file to load and execute in a NodeVM.
523
+ * @return {*} Result of executed code.
524
+ * @throws {Error} If filename is not a valid filename.
525
+ * @throws {SyntaxError} If there is a syntax error in the script.
526
+ * @throws {Error} An error is thrown when the script took to long and there is a timeout.
527
+ * @throws {*} If the script execution terminated with an exception it is propagated.
528
+ */
529
+ runFile(filename) {
530
+ const resolvedFilename = pa.resolve(filename);
531
+
532
+ if (!fs.existsSync(resolvedFilename)) {
533
+ throw new VMError(`Script '${filename}' not found.`);
534
+ }
535
+
536
+ if (fs.statSync(resolvedFilename).isDirectory()) {
537
+ throw new VMError('Script must be file, got directory.');
538
+ }
539
+
540
+ return this.run(fs.readFileSync(resolvedFilename, 'utf8'), resolvedFilename);
541
+ }
542
+
543
+ }
544
+
545
+ exports.VM = VM;
package/package.json ADDED
@@ -0,0 +1,48 @@
1
+ {
2
+ "author": {
3
+ "name": "Patrik Simek",
4
+ "url": "https://patriksimek.cz"
5
+ },
6
+ "name": "@novolobos/nodevm",
7
+ "description": "vm2 is a sandbox that can run untrusted code with whitelisted Node's built-in modules.",
8
+ "keywords": [
9
+ "sandbox",
10
+ "prison",
11
+ "jail",
12
+ "vm",
13
+ "alcatraz",
14
+ "contextify"
15
+ ],
16
+ "version": "3.10.5",
17
+ "main": "index.js",
18
+ "sideEffects": false,
19
+ "repository": "github:patriksimek/vm2",
20
+ "license": "MIT",
21
+ "dependencies": {
22
+ "acorn": "^8.15.0",
23
+ "acorn-walk": "^8.3.4"
24
+ },
25
+ "devDependencies": {
26
+ "eslint": "^9.38.0",
27
+ "mocha": "^11.1.0"
28
+ },
29
+ "engines": {
30
+ "node": ">=6.0"
31
+ },
32
+ "scripts": {
33
+ "test": "mocha test --ignore test/compilers.js",
34
+ "test:compilers": "mocha test/compilers.js",
35
+ "lint": "eslint ."
36
+ },
37
+ "bin": {
38
+ "vm2": "./bin/vm2"
39
+ },
40
+ "types": "index.d.ts",
41
+ "files": [
42
+ "lib",
43
+ "index.js",
44
+ "index.d.ts",
45
+ "LICENSE.md",
46
+ "README.md"
47
+ ]
48
+ }