diff --git a/README.md b/README.md index 6439024..ee80503 100644 --- a/README.md +++ b/README.md @@ -144,6 +144,7 @@ Unlike `VM`, `NodeVM` lets you require modules same way like in regular Node's c * `wrapper` - `commonjs` (default) to wrap script into CommonJS wrapper, `none` to retrieve value returned by the script. * `argv` - Array to be passed to `process.argv`. * `env` - Object to be passed to `process.env`. +* `strict` - `true` to loaded modules in strict mode (default: `false`). **IMPORTANT**: Timeout is not effective for NodeVM so it is not immune to `while (true) {}` or similar evil. diff --git a/lib/main.js b/lib/main.js index 746e499..14e76a9 100644 --- a/lib/main.js +++ b/lib/main.js @@ -27,6 +27,10 @@ const {EventEmitter} = require('events'); const {INSPECT_MAX_BYTES} = require('buffer'); const helpers = require('./helpers.js'); +const MODULE_PREFIX = '(function (exports, require, module, __filename, __dirname) { '; +const STRICT_MODULE_PREFIX = MODULE_PREFIX + '"use strict"; '; +const MODULE_SUFFIX = '\n});'; + /** * Load a script from a file and compile it. * @@ -241,6 +245,14 @@ class VMScript { * @memberOf VMScript# */ + /** + * The compiled vm.Script for the NodeVM in strict mode or if not compiled null. + * + * @private + * @member {?vm.Script} _compiledNodeVMStrict + * @memberOf VMScript# + */ + /** * The resolved compiler to use to get the JavaScript code. * @@ -354,6 +366,10 @@ class VMScript { value: null, writable: true }, + _compiledNodeVMStrict: { + value: null, + writable: true + }, _compiledCode: { value: null, writable: true @@ -382,6 +398,7 @@ class VMScript { this._suffix = strSuffix; this._compiledVM = null; this._compiledNodeVM = null; + this._compiledNodeVMStrict = null; return this; } @@ -454,7 +471,22 @@ class VMScript { _compileNodeVM() { let script = this._compiledNodeVM; if (!script) { - this._compiledNodeVM = script = this._compile('(function (exports, require, module, __filename, __dirname) { ', '\n})'); + this._compiledNodeVM = script = this._compile(MODULE_PREFIX, MODULE_SUFFIX); + } + return script; + } + + /** + * Will return the cached version of the script intended for NodeVM in strict mode or compile it. + * + * @private + * @return {vm.Script} The compiled script + * @throws {SyntaxError} If there is a syntax error in the script. + */ + _compileNodeVMStrict() { + let script = this._compiledNodeVMStrict; + if (!script) { + this._compiledNodeVMStrict = script = this._compile(STRICT_MODULE_PREFIX, MODULE_SUFFIX); } return script; } @@ -1017,6 +1049,7 @@ class NodeVM extends VM { * This object will not be copied and the script can change this object. * @param {Object} [options.env={}] - Environment map passed to process.env. * This object will not be copied and the script can change this object. + * @param {boolean} [options.strict=false] - If modules should be loaded in strict mode. * @throws {VMError} If the compiler is unknown. */ constructor(options = {}) { @@ -1035,7 +1068,8 @@ class NodeVM extends VM { require: options.require || false, nesting: options.nesting || false, wrapper: options.wrapper || 'commonjs', - sourceExtensions: options.sourceExtensions || ['js'] + sourceExtensions: options.sourceExtensions || ['js'], + strict: options.strict || false }}); let sandboxScript = CACHE.sandboxScript; @@ -1123,7 +1157,7 @@ class NodeVM extends VM { let script; if (code instanceof VMScript) { - script = code._compileNodeVM(); + script = this.options.strict ? code._compileNodeVMStrict() : code._compileNodeVM(); resolvedFilename = pa.resolve(code.filename); dirname = pa.dirname(resolvedFilename); } else { @@ -1135,8 +1169,9 @@ class NodeVM extends VM { resolvedFilename = null; dirname = null; } - script = new vm.Script('(function (exports, require, module, __filename, __dirname) { ' + - this._compiler(code, unresolvedFilename) + '\n})', { + const prefix = this.options.strict ? STRICT_MODULE_PREFIX : MODULE_PREFIX; + script = new vm.Script(prefix + + this._compiler(code, unresolvedFilename) + MODULE_SUFFIX, { filename: unresolvedFilename, displayErrors: false }); @@ -1290,7 +1325,10 @@ const HOST = { INSPECT_MAX_BYTES, VM, NodeVM, - helpers + helpers, + MODULE_PREFIX, + STRICT_MODULE_PREFIX, + MODULE_SUFFIX }; exports.VMError = VMError; diff --git a/lib/sandbox.js b/lib/sandbox.js index 8f2c2ab..e19deed 100644 --- a/lib/sandbox.js +++ b/lib/sandbox.js @@ -61,7 +61,7 @@ return ((vm, host) => { let contents = fs.readFileSync(filename, 'utf8'); contents = vm._compiler(contents, filename); - const code = `(function (exports, require, module, __filename, __dirname) { 'use strict'; ${contents} \n});`; + const code = host.STRICT_MODULE_PREFIX + contents + host.MODULE_SUFFIX; // Precompile script script = new Script(code, { diff --git a/test/nodevm.js b/test/nodevm.js index d17582b..b307c46 100644 --- a/test/nodevm.js +++ b/test/nodevm.js @@ -56,6 +56,11 @@ describe('NodeVM', () => { assert.doesNotThrow(() => vm.run('#!shebang')); }); + it('strict', () => { + assert.doesNotThrow(() => vm.run('newGlobal = 2;')); + assert.throws(() => new NodeVM({strict: true}).run('newGlobal = 2;'), /ReferenceError: newGlobal is not defined/); + }); + it.skip('timeout (not supported by Node\'s VM)', () => { assert.throws(() => new NodeVM({ timeout: 10 diff --git a/test/vm.js b/test/vm.js index 5c5d802..b626ed3 100644 --- a/test/vm.js +++ b/test/vm.js @@ -283,26 +283,25 @@ describe('contextify', () => { describe('VM', () => { let vm; - before(() => { - const sandbox = { - round(number) { - return Math.round(number); - }, - sub: {} - }; + const sandbox = { + round(number) { + return Math.round(number); + }, + sub: {} + }; - Object.defineProperty(sandbox.sub, 'getter', { - get() { - const results = []; - while (true) { - results.push(1); - } - return results; + Object.defineProperty(sandbox.sub, 'getter', { + get() { + const results = []; + while (true) { + results.push(1); } - }); + return results; + } + }); + before(() => { vm = new VM({ - timeout: 10, sandbox }); }); @@ -345,7 +344,7 @@ describe('VM', () => { assert.throws(() => new VM({ timeout: 10 }).run('while (true) {}'), message); - assert.throws(() => vm.run('sub.getter'), message); + assert.throws(() => new VM({timeout: 10, sandbox}).run('sub.getter'), message); }); it('timers', () => {