| 1 + 1; |
| {"working": true} |
Sorry, the diff of this file is not supported yet
+852
| const assert = require("assert"); | ||
| const {NodeVM, VM, VMScript} = require('..'); | ||
| global.isVM = false; | ||
| describe('contextify', () => { | ||
| let vm; | ||
| class TestClass { | ||
| constructor() { | ||
| this.greeting = 'hello'; | ||
| } | ||
| greet(name) { | ||
| return `${this.greeting} ${name}`; | ||
| } | ||
| } | ||
| let sandbox = { | ||
| assert, | ||
| test: { | ||
| string: "text", | ||
| stringO: new String("text"), | ||
| number: 1, | ||
| numberO: new Number(1), | ||
| boolean: true, | ||
| booleanO: new Boolean(true), | ||
| date: new Date(), | ||
| regexp: /xxx/, | ||
| buffer: new Buffer([0x00, 0x01]), | ||
| "function"() { return () => ({}) }, | ||
| object: { | ||
| x: 1, | ||
| y() { return i => i instanceof Object; }, | ||
| z(i) { | ||
| if (!(i instanceof Object)) throw new Error("Not instanceof parent Object."); | ||
| return i; | ||
| } | ||
| }, | ||
| nil: null, | ||
| undef: void 0, | ||
| klass: TestClass, | ||
| symbol1: Symbol('foo'), | ||
| symbol2: Symbol.for('foo'), | ||
| symbol3: Symbol.iterator, | ||
| error: new Error('test') | ||
| } | ||
| } | ||
| before(() => { | ||
| vm = new VM({sandbox}); | ||
| }) | ||
| it('common', () => { | ||
| assert.ok(vm.run(`global.__proto__ === Object.prototype`)); | ||
| assert.ok(vm.run(`global.__proto__.constructor === Object`)); | ||
| assert.ok(vm.run(`Object.__proto__ === Function.prototype`)); | ||
| assert.ok(vm.run(`Object.__proto__.constructor === Function`)); | ||
| assert.ok(vm.run(`Object.prototype.__proto__ === null`)); | ||
| assert.ok(vm.run(`Function.__proto__ === Function.prototype`)); | ||
| assert.ok(vm.run(`Function.__proto__.constructor === Function`)); | ||
| assert.ok(vm.run(`Function.prototype.__proto__ === Object.prototype`)); | ||
| assert.ok(vm.run(`Array.__proto__ === Function.prototype`)); | ||
| assert.ok(vm.run(`Array.__proto__.constructor === Function`)); | ||
| assert.ok(vm.run(`Array.prototype.__proto__ === Object.prototype`)); | ||
| assert.strictEqual(sandbox.test.object.y === sandbox.test.object.y.valueOf(), true); | ||
| assert.strictEqual(vm.run("test.object.y instanceof Function"), true); | ||
| assert.strictEqual(vm.run("test.object.y.valueOf() instanceof Function"), true); | ||
| assert.strictEqual(vm.run("test.object.y").isVMProxy, void 0); | ||
| assert.strictEqual(vm.run("test.object.y.valueOf()").isVMProxy, void 0); | ||
| assert.strictEqual(vm.run("test.object.y") === vm.run("test.object.y.valueOf()"), true); | ||
| assert.strictEqual(vm.run("test.object.y === test.object.y.valueOf()"), true); | ||
| assert.strictEqual(vm.run("test.object").y instanceof Function, true); | ||
| assert.strictEqual(vm.run("test.object").y.valueOf() instanceof Function, true); | ||
| assert.strictEqual(vm.run("test.object").y.isVMProxy, void 0); | ||
| assert.strictEqual(vm.run("test.object").y.valueOf().isVMProxy, void 0); | ||
| assert.strictEqual(vm.run("test.object").y === vm.run("test.object").y.valueOf(), true); | ||
| assert.strictEqual(vm.run("test.valueOf()") === vm.run("test").valueOf(), true); | ||
| assert.strictEqual(vm.run("test.object.y.constructor instanceof Function"), true); | ||
| assert.strictEqual(vm.run("test.object.y.constructor('return (function(){return this})().isVM')()"), true); | ||
| assert.strictEqual(vm.run("test.object.valueOf() instanceof Object"), true); | ||
| assert.strictEqual(vm.run("test.object.valueOf().y instanceof Function"), true); | ||
| assert.strictEqual(vm.run("test.object.valueOf().y.constructor instanceof Function"), true); | ||
| assert.strictEqual(vm.run("test.object.valueOf().y.constructor('return (function(){return this})().isVM')()"), true); | ||
| let o = vm.run("let x = {a: test.date, b: test.date};x"); | ||
| assert.strictEqual(vm.run("x.valueOf().a instanceof Date"), true); | ||
| assert.strictEqual(o instanceof Object, true); | ||
| assert.strictEqual(o.a instanceof Date, true); | ||
| assert.strictEqual(o.b instanceof Date, true); | ||
| assert.strictEqual(o.a === o.b, true); | ||
| assert.strictEqual(o.a === sandbox.test.date, true); | ||
| o = vm.run("let y = new Date(); let z = {a: y, b: y};z"); | ||
| assert.strictEqual(o.isVMProxy, true); | ||
| assert.strictEqual(o instanceof Object, true); | ||
| assert.strictEqual(o.a instanceof Date, true); | ||
| assert.strictEqual(o.b instanceof Date, true); | ||
| assert.strictEqual(o.a === o.b, true); | ||
| }) | ||
| it('class', () => { | ||
| assert.strictEqual(vm.run("new test.klass()").isVMProxy, undefined); | ||
| assert.strictEqual(vm.run("new test.klass()").greet('friend'), 'hello friend'); | ||
| assert.strictEqual(vm.run("new test.klass()") instanceof TestClass, true); | ||
| //vm.run("class LocalClass extends test.klass {}"); | ||
| }) | ||
| it('string', () => { | ||
| assert.strictEqual(vm.run("(test.string).constructor === String"), true); | ||
| assert.strictEqual(vm.run("typeof(test.stringO) === 'string' && test.string.valueOf instanceof Object"), true); | ||
| }) | ||
| it('number', () => { | ||
| assert.strictEqual(vm.run("typeof(test.numberO) === 'number' && test.number.valueOf instanceof Object"), true); | ||
| }) | ||
| it('boolean', () => { | ||
| assert.strictEqual(vm.run("typeof(test.booleanO) === 'boolean' && test.boolean.valueOf instanceof Object"), true); | ||
| }) | ||
| it('date', () => { | ||
| assert.strictEqual(vm.run("test.date instanceof Date"), true); | ||
| assert.strictEqual(vm.run("test.date") instanceof Date, true); | ||
| assert.strictEqual(vm.run("test.date"), sandbox.test.date); | ||
| }) | ||
| it('regexp', () => { | ||
| assert.strictEqual(vm.run("test.regexp instanceof RegExp"), true); | ||
| }) | ||
| it('buffer', () => { | ||
| assert.strictEqual(vm.run("test.buffer.inspect()"), '<Buffer 00 01>', '#1'); | ||
| assert.strictEqual(vm.run("test.buffer instanceof Buffer"), true, '#2'); | ||
| assert.strictEqual(vm.run("test.buffer") instanceof Buffer, true, '#3'); | ||
| assert.strictEqual(vm.run("test.buffer"), sandbox.test.buffer, '#4'); | ||
| assert.strictEqual(vm.run("class Buffer2 extends Buffer {};new Buffer2(5)").fill(1).inspect(), '<Buffer 01 01 01 01 01>'); | ||
| let {a, b, c, d} = vm.run(` | ||
| let a = new Buffer([0x01, 0x02]); | ||
| let b = Buffer.alloc(3, 0x03); | ||
| let c = Buffer.from(a); | ||
| let d = Buffer.concat([a, b, c]); | ||
| assert.ok(a instanceof Buffer, '#1'); | ||
| assert.ok(b instanceof Buffer, '#2'); | ||
| assert.ok(c instanceof Buffer, '#3'); | ||
| assert.ok(d instanceof Buffer, '#4'); | ||
| assert.ok(a.constructor === Buffer, '#5'); | ||
| assert.ok(b.constructor === Buffer, '#6'); | ||
| assert.ok(c.constructor === Buffer, '#7'); | ||
| assert.ok(d.constructor === Buffer, '#8'); | ||
| assert.ok(a.constructor.constructor === Function, '#9'); | ||
| assert.ok(b.constructor.constructor === Function, '#10'); | ||
| assert.ok(c.constructor.constructor === Function, '#11'); | ||
| assert.ok(d.constructor.constructor === Function, '#12'); | ||
| ({a: a, b: b, c: c, d: d}) | ||
| `); | ||
| assert.ok(a instanceof Buffer); | ||
| assert.ok(b instanceof Buffer); | ||
| assert.ok(c instanceof Buffer); | ||
| assert.ok(d instanceof Buffer); | ||
| assert.ok(a.constructor === Buffer); | ||
| assert.ok(b.constructor === Buffer); | ||
| assert.ok(c.constructor === Buffer); | ||
| assert.ok(d.constructor === Buffer); | ||
| assert.ok(a.constructor.constructor === Function); | ||
| assert.ok(b.constructor.constructor === Function); | ||
| assert.ok(c.constructor.constructor === Function); | ||
| assert.ok(d.constructor.constructor === Function); | ||
| }) | ||
| it('function', () => { | ||
| assert.strictEqual(vm.run("test.function instanceof Function"), true, '#1'); | ||
| assert.strictEqual(vm.run("test.function() instanceof Function"), true, '#2'); | ||
| assert.strictEqual(vm.run("test.function()() instanceof Object"), true, '#3'); | ||
| }) | ||
| it('object', () => { | ||
| assert.strictEqual(vm.run("test.object instanceof Object && test.object.x === 1"), true, '#1'); | ||
| assert.strictEqual(vm.run("test.object.y instanceof Function"), true, '#2'); | ||
| assert.strictEqual(vm.run("test.object.y() instanceof Function"), true, '#3'); | ||
| assert.strictEqual(vm.run("test.object.y()({})"), true, '#4'); | ||
| assert.strictEqual(vm.run("test.object.z({}) instanceof Object"), true, '#5'); | ||
| assert.strictEqual(vm.run("Object.getOwnPropertyDescriptor(test.object, 'y').hasOwnProperty instanceof Function"), true, '#6'); | ||
| assert.strictEqual(vm.run("Object.getOwnPropertyDescriptor(test.object, 'y').hasOwnProperty.constructor('return (function(){return this})().isVM')()"), true, '#7'); | ||
| }) | ||
| it('null', () => { | ||
| assert.strictEqual(vm.run("test.nil === null"), true); | ||
| }) | ||
| it('undefined', () => { | ||
| assert.strictEqual(vm.run("test.undef === undefined"), true); | ||
| }) | ||
| it('symbol', () => { | ||
| assert.strictEqual(vm.run("Symbol.for('foo') === test.symbol2"), true); | ||
| assert.strictEqual(vm.run("test.symbol1.constructor.constructor === Function"), true); | ||
| assert.strictEqual(vm.run("test.symbol2.constructor.constructor === Function"), true); | ||
| assert.strictEqual(vm.run("test.symbol3.constructor.constructor === Function"), true); | ||
| assert.strictEqual(vm.run("Symbol('foo').constructor.constructor === Function"), true); | ||
| assert.strictEqual(vm.run("Symbol('foobar').constructor.constructor === Function"), true); | ||
| assert.strictEqual(vm.run("Symbol.keyFor(test.symbol2)"), 'foo'); | ||
| }) | ||
| it('error', () => { | ||
| assert.strictEqual(vm.run("test.error.constructor.constructor === Function;"), true); | ||
| }) | ||
| after(() => { | ||
| vm = null; | ||
| }) | ||
| }) | ||
| describe('VM', () => { | ||
| let vm; | ||
| before(() => { | ||
| let sandbox = { | ||
| round(number) { return Math.round(number); }, | ||
| sub: {} | ||
| } | ||
| Object.defineProperty(sandbox.sub, 'getter', { | ||
| get() { | ||
| let results; | ||
| results = []; | ||
| while (true) { | ||
| results.push(1); | ||
| } | ||
| return results; | ||
| } | ||
| }) | ||
| vm = new VM({ | ||
| timeout: 10, | ||
| sandbox | ||
| }) | ||
| }) | ||
| it('globals', () => { | ||
| assert.equal(vm.run("round(1.5)"), 2); | ||
| }) | ||
| it('errors', () => { | ||
| assert.throws(() => vm.run("notdefined"), /notdefined is not defined/); | ||
| assert.throws(() => vm.run("Object.setPrototypeOf(sub, {})"), err => { | ||
| assert.equal(err.name, 'VMError'); | ||
| assert.equal(err.message, 'Operation not allowed on contextified object.'); | ||
| return true; | ||
| }) | ||
| }) | ||
| it('timeout', () => { | ||
| assert.throws(() => new VM({ | ||
| timeout: 10 | ||
| }).run("while (true) {}"), /Script execution timed out\./); | ||
| assert.throws(() => vm.run("sub.getter"), /Script execution timed out\./); | ||
| }) | ||
| it('timers', () => { | ||
| assert.equal(vm.run("global.setTimeout"), void 0); | ||
| assert.equal(vm.run("global.setInterval"), void 0); | ||
| assert.equal(vm.run("global.setImmediate"), void 0); | ||
| }) | ||
| it('various attacks #1', () => { | ||
| let vm2 = new VM({sandbox: {log: console.log, boom: function() { throw new Error(); }}}); | ||
| assert.strictEqual(vm2.run("this.constructor.constructor('return Function(\\'return Function\\')')()() === this.constructor.constructor('return Function')()"), true); | ||
| assert.throws(() => vm2.run(` | ||
| const ForeignFunction = global.constructor.constructor; | ||
| const process1 = ForeignFunction("return process")(); | ||
| `), /process is not defined/, '#1'); | ||
| assert.throws(() => vm2.run(` | ||
| try { | ||
| boom(); | ||
| } | ||
| catch (e) { | ||
| const foreignFunction = e.constructor.constructor; | ||
| const process = foreignFunction("return process")(); | ||
| } | ||
| `), /process is not defined/, '#2'); | ||
| assert.doesNotThrow(() => vm2.run(` | ||
| function exploit(o) { | ||
| throw new Error('Shouldnt be there.'); | ||
| } | ||
| Reflect.construct = exploit; | ||
| new Buffer([0]); | ||
| `), '#3'); | ||
| assert.doesNotThrow(() => vm2.run(` | ||
| global.Proxy = function() { | ||
| throw new Error('Shouldnt be there.'); | ||
| } | ||
| `), '#4'); | ||
| assert.doesNotThrow(() => vm2.run(` | ||
| global.String = function(text) { | ||
| throw new Error('Shouldnt be there.'); | ||
| };(function(text) {}) | ||
| `)('asdf'), '#5'); | ||
| assert.doesNotThrow(() => vm2.run(` | ||
| global.String = function(text) { | ||
| throw new Error('Shouldnt be there.'); | ||
| };(function(text) {}) | ||
| `)(new String('asdf')), '#6'); | ||
| assert.doesNotThrow(() => vm2.run(` | ||
| global.Buffer = function(value) { | ||
| throw new Error('Shouldnt be there.'); | ||
| };(function(value) {}) | ||
| `)(new Buffer(1)), '#7'); | ||
| }) | ||
| it('various attacks #2', () => { | ||
| let vm2 = new VM({ | ||
| sandbox: { | ||
| boom: function() {}, | ||
| error: new Error('test') | ||
| } | ||
| }); | ||
| assert.doesNotThrow(() => vm2.run(` | ||
| Object.assign = function (o) { | ||
| throw new Error('Shouldnt be there.'); | ||
| }; | ||
| new Buffer([0]); | ||
| `), '#1'); | ||
| assert.doesNotThrow(() => vm2.run(` | ||
| try { | ||
| new Buffer(); | ||
| } catch (e) { | ||
| if (e.constructor.constructor !== Function) throw new Error('Shouldnt be there.'); | ||
| } | ||
| `), '#2'); | ||
| assert.doesNotThrow(() => vm2.run(` | ||
| let o; | ||
| Array.prototype.map = function(callback) { | ||
| o = callback(boom); | ||
| return []; | ||
| }; | ||
| boom(boom); | ||
| if (o && o.constructor !== Function) throw new Error('Shouldnt be there.'); | ||
| `), '#3'); | ||
| assert.doesNotThrow(() => vm2.run(` | ||
| let method = () => {}; | ||
| let proxy = new Proxy(method, { | ||
| apply: (target, context, args) => { | ||
| if (target.constructor.constructor !== Function) throw new Error('Shouldnt be there.'); | ||
| if (args.constructor.constructor !== Function) throw new Error('Shouldnt be there.'); | ||
| } | ||
| }); | ||
| proxy | ||
| `)('asdf'), '#4'); | ||
| assert.doesNotThrow(() => vm2.run(` | ||
| let proxy2 = new Proxy(function() {}, { | ||
| apply: (target, context, args) => { | ||
| if (args.constructor.constructor !== Function) throw new Error('Shouldnt be there.'); | ||
| } | ||
| }); | ||
| proxy2 | ||
| `)('asdf'), '#5'); | ||
| assert.strictEqual(vm2.run(` | ||
| global.DEBUG = true; | ||
| boom.vmProxyTarget | ||
| `), undefined, '#6'); | ||
| assert.throws(() => vm2.run(` | ||
| global.constructor.constructor('return this')().constructor.constructor('return process')() | ||
| `), /process is not defined/, '#7'); | ||
| assert.throws(() => vm2.run(` | ||
| global.__proto__.constructor.constructor('return this')().constructor.constructor('return process')() | ||
| `), /process is not defined/, '#8'); | ||
| assert.doesNotThrow(() => vm2.run(` | ||
| if (!(Object.keys(boom) instanceof Array)) throw new Error('Shouldnt be there.'); | ||
| if (!(Reflect.ownKeys(boom) instanceof Array)) throw new Error('Shouldnt be there.'); | ||
| `)); | ||
| }) | ||
| it('buffer attack', () => { | ||
| let vm2 = new VM(); | ||
| assert.strictEqual(vm2.run(` | ||
| new Buffer(100).toString('hex'); | ||
| `), '00'.repeat(100), '#1'); | ||
| assert.strictEqual(vm2.run(` | ||
| Buffer.allocUnsafe(100).constructor.constructor === Function; | ||
| `), true, '#2'); | ||
| assert.strictEqual(vm2.run(` | ||
| Buffer.allocUnsafe(100).toString('hex'); | ||
| `), '00'.repeat(100), '#3'); | ||
| assert.strictEqual(vm2.run(` | ||
| class MyBuffer extends Buffer {}; new MyBuffer(100).toString('hex'); | ||
| `), '00'.repeat(100), '#4'); | ||
| }) | ||
| after(() => { | ||
| vm = null; | ||
| }) | ||
| }) | ||
| describe('NodeVM', () => { | ||
| let vm; | ||
| before(() => { | ||
| vm = new NodeVM; | ||
| }) | ||
| it('globals', () => { | ||
| let ex; | ||
| ex = vm.run("module.exports = global"); | ||
| assert.equal(ex.isVM, true); | ||
| }) | ||
| it('errors', () => { | ||
| assert.throws(() => vm.run("notdefined"), /notdefined is not defined/); | ||
| }) | ||
| it('prevent global access', () => { | ||
| assert.throws(() => vm.run("process.exit()"), /(undefined is not a function|process\.exit is not a function)/); | ||
| }) | ||
| it('arguments attack', () => { | ||
| assert.strictEqual(vm.run("module.exports = (function() { return arguments.callee.caller.constructor === Function; })()"), true); | ||
| assert.throws(() => vm.run("module.exports = (function() { return arguments.callee.caller.caller.toString(); })()"), /Cannot read property 'toString' of null/); | ||
| }) | ||
| it('global attack', () => { | ||
| assert.equal(vm.run("module.exports = console.log.constructor('return (function(){return this})().isVM')()"), true); | ||
| }) | ||
| it.skip('timeout (not supported by Node\'s VM)', () => { | ||
| assert.throws(() => new NodeVM({ | ||
| timeout: 10 | ||
| }).run("while (true) {}"), /Script execution timed out\./); | ||
| }) | ||
| after(() => { | ||
| vm = null; | ||
| }) | ||
| }) | ||
| describe('modules', () => { | ||
| it('require json', () => { | ||
| let vm = new NodeVM({ | ||
| require: { | ||
| external: true | ||
| } | ||
| }) | ||
| assert.equal(vm.run(`module.exports = require('${__dirname}/data/json.json')`).working, true); | ||
| }) | ||
| it.skip('run coffee-script', () => { | ||
| let vm = new NodeVM({ | ||
| require: { | ||
| external: true | ||
| }, | ||
| compiler: 'coffeescript' | ||
| }) | ||
| assert.equal(vm.run("module.exports = working: true").working, true); | ||
| }) | ||
| it('optionally can run a custom compiler function', () => { | ||
| var ranCustomCompiler = false | ||
| const scriptCode = 'var a = 1;' | ||
| let vm = new NodeVM({ | ||
| compiler: (code) => { | ||
| ranCustomCompiler = true | ||
| assert.equal(code, scriptCode) | ||
| } | ||
| }) | ||
| vm.run(scriptCode) | ||
| assert.equal(ranCustomCompiler, true); | ||
| }) | ||
| it('optionally passes a filename to a custom compiler function', () => { | ||
| var ranCustomCompiler = false | ||
| let vm = new NodeVM({ | ||
| compiler: (code, filename) => { | ||
| ranCustomCompiler = true | ||
| assert.equal(filename, '/a/b/c.js') | ||
| } | ||
| }) | ||
| vm.run("module.exports = working: true", '/a/b/c.js') | ||
| assert.equal(ranCustomCompiler, true); | ||
| }) | ||
| it('disabled require', () => { | ||
| let vm = new NodeVM; | ||
| assert.throws(() => vm.run("require('fs')"), /Access denied to require 'fs'/); | ||
| }) | ||
| it('disable setters on builtin modules', () => { | ||
| let vm = new NodeVM({ | ||
| require: { | ||
| builtin: ['fs'] | ||
| } | ||
| }) | ||
| vm.run("require('fs').readFileSync = undefined"); | ||
| assert.strictEqual(require('fs').readFileSync instanceof Function, true); | ||
| vm.run("require('fs').readFileSync.thisPropertyShouldntBeThere = true"); | ||
| assert.strictEqual(require('fs').readFileSync.thisPropertyShouldntBeThere, undefined); | ||
| assert.throws(() => vm.run("Object.defineProperty(require('fs'), 'test', {})"), err => { | ||
| assert.ok(err instanceof TypeError); | ||
| assert.equal(err.name, 'TypeError'); | ||
| assert.equal(err.message, '\'defineProperty\' on proxy: trap returned falsish for property \'test\''); | ||
| return true; | ||
| }) | ||
| assert.throws(() => vm.run("'use strict'; delete require('fs').readFileSync"), err => { | ||
| assert.ok(err instanceof TypeError); | ||
| assert.equal(err.name, 'TypeError'); | ||
| assert.equal(err.message, '\'deleteProperty\' on proxy: trap returned falsish for property \'readFileSync\''); | ||
| return true; | ||
| }) | ||
| }) | ||
| it('enabled require for certain modules', () => { | ||
| let vm = new NodeVM({ | ||
| require: { | ||
| builtin: ['fs'] | ||
| } | ||
| }) | ||
| assert.doesNotThrow(() => vm.run("require('fs')")); | ||
| }) | ||
| it('require relative', () => { | ||
| let vm = new NodeVM({ | ||
| require: { | ||
| external: true | ||
| }, | ||
| }) | ||
| vm.run("require('foobar')", __filename); | ||
| }) | ||
| it('can require a module inside the vm', () => { | ||
| let vm = new NodeVM({ | ||
| require: { | ||
| external: true | ||
| } | ||
| }) | ||
| vm.run("require('mocha')", __filename); | ||
| }) | ||
| it('can deny requiring modules inside the vm', () => { | ||
| let vm = new NodeVM({ | ||
| require: { | ||
| external: false | ||
| }, | ||
| }) | ||
| assert.throws(() => vm.run("require('mocha')", __filename), err => { | ||
| assert.equal(err.name, 'VMError'); | ||
| assert.equal(err.message, 'Access denied to require \'mocha\''); | ||
| return true; | ||
| }) | ||
| }) | ||
| it('can whitelist modules inside the vm', () => { | ||
| let vm = new NodeVM({ | ||
| require: { | ||
| external: ['mocha'] | ||
| } | ||
| }) | ||
| assert.ok(vm.run("require('mocha')", __filename)) | ||
| assert.throws(() => vm.run("require('unknown')", __filename), err => { | ||
| assert.equal(err.name, 'VMError'); | ||
| assert.equal(err.message, "The module 'unknown' is not whitelisted in VM."); | ||
| return true; | ||
| }) | ||
| }) | ||
| it('arguments attack', () => { | ||
| let vm = new NodeVM; | ||
| assert.throws(() => vm.run("module.exports = function fce(msg) { return arguments.callee.caller.toString(); }")(), /Cannot read property 'toString' of null/); | ||
| vm = new NodeVM; | ||
| assert.throws(() => vm.run("module.exports = function fce(msg) { return fce.caller.toString(); }")(), /Cannot read property 'toString' of null/); | ||
| }) | ||
| it('builtin module arguments attack', done => { | ||
| let vm = new NodeVM({ | ||
| require: { | ||
| builtin: ['fs'] | ||
| }, | ||
| sandbox: { | ||
| parentfilename: __filename, | ||
| done | ||
| } | ||
| }) | ||
| vm.run("var fs = require('fs'); fs.exists(parentfilename, function() {try {arguments.callee.caller.toString()} catch (err) {return done();}; done(new Error('Missing expected exception'))})"); | ||
| }) | ||
| it('path attack', () => { | ||
| let vm = new NodeVM({ | ||
| require: { | ||
| external: true, | ||
| root: __dirname | ||
| } | ||
| }) | ||
| assert.throws(() => vm.run("var test = require('../package.json')", __filename), /Module '\.\.\/package.json' is not allowed to be required\. The path is outside the border!/); | ||
| }) | ||
| it('process events', () => { | ||
| let vm = new NodeVM({ | ||
| sandbox: { | ||
| VM2_COUNTER: 0 | ||
| } | ||
| }) | ||
| let sandbox = vm.run("global.VM2_HANDLER = function() { VM2_COUNTER++ }; process.on('exit', VM2_HANDLER); module.exports = global;"); | ||
| process.emit('exit'); | ||
| assert.strictEqual(sandbox.VM2_COUNTER, 1); | ||
| assert.strictEqual(vm.run("module.exports = process.listeners('exit')[0] === VM2_HANDLER;"), true); | ||
| vm.run("process.removeListener('exit', VM2_HANDLER);"); | ||
| process.emit('exit'); | ||
| assert.strictEqual(sandbox.VM2_COUNTER, 1); | ||
| }) | ||
| it('timers', done => { | ||
| let vm = new NodeVM({ | ||
| sandbox: { | ||
| done | ||
| } | ||
| }) | ||
| vm.run('let i = setImmediate(function() { global.TICK = true; });clearImmediate(i);'); | ||
| setImmediate(() => { | ||
| assert.strictEqual(vm.run('module.exports = global.TICK'), void 0); | ||
| vm.run('setImmediate(done);'); | ||
| }) | ||
| }) | ||
| it('mock', () => { | ||
| let vm = new NodeVM({ | ||
| require: { | ||
| mock: { | ||
| fs: { | ||
| readFileSync() { return 'Nice try!'; } | ||
| } | ||
| } | ||
| } | ||
| }) | ||
| assert.strictEqual(vm.run("module.exports = require('fs').constructor.constructor === Function"), true); | ||
| assert.strictEqual(vm.run("module.exports = require('fs').readFileSync.constructor.constructor === Function"), true); | ||
| assert.strictEqual(vm.run("module.exports = require('fs').readFileSync()"), 'Nice try!'); | ||
| }) | ||
| }) | ||
| describe('nesting', () => { | ||
| it('NodeVM', () => { | ||
| let vm = new NodeVM({ | ||
| nesting: true | ||
| }) | ||
| let nestedObject = vm.run(` | ||
| const {VM} = require('vm2'); | ||
| const vm = new VM(); | ||
| let o = vm.run('({})'); | ||
| module.exports = o; | ||
| `, 'vm.js'); | ||
| assert.strictEqual(nestedObject.constructor.constructor === Function, true); | ||
| }) | ||
| }) | ||
| describe('wrappers', () => { | ||
| it('none', () => { | ||
| let vm = new NodeVM({ | ||
| wrapper: 'none' | ||
| }) | ||
| assert.strictEqual(vm.run('return 2 + 2'), 4) | ||
| }) | ||
| }) | ||
| describe('precompiled scripts', () => { | ||
| it('VM', () => { | ||
| let vm = new VM(); | ||
| let script = new VMScript("Math.random()"); | ||
| let val1 = vm.run(script); | ||
| let val2 = vm.run(script); | ||
| assert.ok('number' === typeof val1 && 'number' === typeof val2); | ||
| assert.ok( val1 != val2); | ||
| }) | ||
| it('NodeVM', () => { | ||
| let vm = new NodeVM(); | ||
| let script = new VMScript("module.exports = Math.random()"); | ||
| let val1 = vm.run(script); | ||
| let val2 = vm.run(script); | ||
| assert.ok('number' === typeof val1 && 'number' === typeof val2); | ||
| assert.ok( val1 != val2); | ||
| }) | ||
| }) | ||
| describe('freeze, protect', () => { | ||
| it('without freeze', () => { | ||
| let x = { | ||
| a: () => 'a', | ||
| b: () => 'b', | ||
| c: { | ||
| d: () => 'd' | ||
| } | ||
| } | ||
| let vm = new VM({ | ||
| sandbox: {x} | ||
| }); | ||
| vm.run('x.a = () => { return `-` }; x.c.d = () => { return `---` }; (y) => { y.b = () => { return `--` } }')(x); | ||
| assert.strictEqual(x.a(), '-'); | ||
| assert.strictEqual(x.b(), '--'); | ||
| assert.strictEqual(x.c.d(), '---'); | ||
| }) | ||
| it('with freeze', () => { | ||
| let x = { | ||
| a: () => 'a', | ||
| b: () => 'b', | ||
| c: { | ||
| d: () => 'd' | ||
| } | ||
| }; | ||
| let vm = new VM(); | ||
| vm.freeze(x, 'x'); | ||
| assert.throws(() => { | ||
| vm.run('"use strict"; x.a = () => { return `-` };'); | ||
| }, /'set' on proxy: trap returned falsish for property 'a'/); | ||
| assert.throws(() => { | ||
| vm.run('"use strict"; (y) => { y.b = () => { return `--` } }')(x); | ||
| }, /'set' on proxy: trap returned falsish for property 'b'/); | ||
| assert.throws(() => { | ||
| vm.run('"use strict"; x.c.d = () => { return `---` };'); | ||
| }, /'set' on proxy: trap returned falsish for property 'd'/); | ||
| vm.run('x.a = () => { return `-` };'); | ||
| assert.strictEqual(x.a(), 'a'); | ||
| vm.run('(y) => { y.b = () => { return `--` } }')(x); | ||
| assert.strictEqual(x.b(), 'b'); | ||
| vm.run('x.c.d = () => { return `---` };'); | ||
| assert.strictEqual(x.c.d(), 'd'); | ||
| }) | ||
| it('without protect', () => { | ||
| let vm = new VM(), obj = {}; | ||
| vm.run('(i) => { i.text = "test" }')(obj); | ||
| vm.run('(i) => { i.func = () => {} }')(obj); | ||
| vm.run('(i) => { delete i.func }')(obj); | ||
| }) | ||
| it('with protect', () => { | ||
| let vm = new VM(), obj = { | ||
| date: new Date(), | ||
| array: [{},{}] | ||
| }; | ||
| vm.protect(obj); | ||
| vm.run('(i) => { i.func = () => {} }')(obj); | ||
| assert.strictEqual(typeof obj.func, 'undefined'); | ||
| assert.throws(() => { | ||
| vm.run('"use strict"; (i) => { i.func = () => {} }')(obj); | ||
| }); | ||
| vm.run('(i) => { i.array.func = () => {} }')(obj); | ||
| assert.strictEqual(typeof obj.array.func, 'undefined'); | ||
| assert.throws(() => { | ||
| vm.run('"use strict"; (i) => { i.array.func = () => {} }')(obj); | ||
| }); | ||
| vm.run('(i) => { i.array[0].func = () => {} }')(obj); | ||
| assert.strictEqual(typeof obj.array[0].func, 'undefined'); | ||
| assert.throws(() => { | ||
| vm.run('"use strict"; (i) => { i.array[0].func = () => {} }')(obj); | ||
| }); | ||
| assert.strictEqual(vm.run('(i) => i.array.map(item => 1).join(",")')(obj), '1,1'); | ||
| assert.strictEqual(vm.run('(i) => /x/.test(i.date)')(obj), false); | ||
| }) | ||
| }) | ||
| describe('source extensions', () => { | ||
| it('does not find a TS module with the default settings', () => { | ||
| let vm = new NodeVM({ | ||
| require: { | ||
| external: true | ||
| } | ||
| }) | ||
| assert.throws(() => { | ||
| vm.run("require('./data/custom_extension')", __filename); | ||
| }) | ||
| }) | ||
| it('finds a TS module with source extensions set', () => { | ||
| let vm = new NodeVM({ | ||
| require: { | ||
| external: true | ||
| }, | ||
| sourceExtensions: ["ts", "js"] | ||
| }) | ||
| vm.run("require('./data/custom_extension')", __filename); | ||
| }) | ||
| }) |
+8
-0
@@ -0,1 +1,9 @@ | ||
| v3.6.0 (2018-05-11) | ||
| ------------------- | ||
| [new] Support for custom source extensions | ||
| [new] WIP support for disallowing Promise | ||
| [fix] Prevent slow unsafe alloc for Buffers | ||
| [fix] Refactors around defaults | ||
| [fix] Types definition update | ||
| v3.5.2 (2017-10-04) | ||
@@ -2,0 +10,0 @@ ------------------- |
+5
-2
@@ -44,2 +44,5 @@ /** | ||
| timeout?: number; | ||
| /** File extensions that the internal module resolver should accept. */ | ||
| sourceExtensions?: string[] | ||
| } | ||
@@ -69,3 +72,3 @@ | ||
| /** Runs the VMScript object */ | ||
| run(script: VMScript): any; | ||
| run(script: VMScript, path?: string): any; | ||
@@ -120,3 +123,3 @@ /** Freezes the object inside VM making it read-only. Not available for primitive values. */ | ||
| export class VMScript { | ||
| constructor(code: string, path: string); | ||
| constructor(code: string, path?: string); | ||
| /** Wraps the code */ | ||
@@ -123,0 +126,0 @@ wrap(prefix: string, postfix: string): VMScript; |
@@ -232,3 +232,3 @@ 'use strict' | ||
| } else if (value instanceof WeakSet) { return Decontextify.instance(value, host.WeakSet, deepTraps, flags); | ||
| } else if (value instanceof Promise) { return Decontextify.instance(value, host.Promise, deepTraps, flags); | ||
| } else if (Promise && value instanceof Promise) { return Decontextify.instance(value, host.Promise, deepTraps, flags); | ||
| } else { | ||
@@ -235,0 +235,0 @@ return Decontextify.object(value, traps, deepTraps, flags, mock); |
+13
-12
@@ -62,5 +62,5 @@ const fs = require('fs'); | ||
| wrap(prefix, postfix) { | ||
| wrap(prefix, suffix) { | ||
| if (this._wrapped) return this; | ||
| this.code = prefix + this.code + postfix; | ||
| this.code = prefix + this.code + suffix; | ||
| this._wrapped = true; | ||
@@ -115,5 +115,5 @@ return this; | ||
| this.options = { | ||
| timeout: options.timeout != null ? options.timeout : undefined, | ||
| sandbox: options.sandbox != null ? options.sandbox : null, | ||
| compiler: options.compiler != null ? options.compiler : 'javascript' | ||
| timeout: options.timeout, | ||
| sandbox: options.sandbox, | ||
| compiler: options.compiler || 'javascript' | ||
| }; | ||
@@ -248,9 +248,10 @@ | ||
| this.options = { | ||
| sandbox: options.sandbox != null ? options.sandbox : null, | ||
| console: options.console != null ? options.console : 'inherit', | ||
| require: options.require != null ? options.require : false, | ||
| compiler: options.compiler != null ? options.compiler : 'javascript', | ||
| require: options.require != null ? options.require : false, | ||
| nesting: options.nesting != null ? options.nesting : false, | ||
| wrapper: options.wrapper != null ? options.wrapper : 'commonjs' | ||
| sandbox: options.sandbox, | ||
| console: options.console || 'inherit', | ||
| require: options.require || false, | ||
| compiler: options.compiler || 'javascript', | ||
| require: options.require || false, | ||
| nesting: options.nesting || false, | ||
| wrapper: options.wrapper || 'commonjs', | ||
| sourceExtensions: options.sourceExtensions || ['js'] | ||
| }; | ||
@@ -257,0 +258,0 @@ |
+18
-7
@@ -39,4 +39,10 @@ const {Script} = host.require('vm'); | ||
| } | ||
| }, | ||
| [".js"](module, filename, dirname) { | ||
| } | ||
| }; | ||
| for (var i = 0; i < vm.options.sourceExtensions.length; i++) { | ||
| var ext = vm.options.sourceExtensions[i]; | ||
| EXTENSIONS["." + ext] = (module, filename, dirname) => { | ||
| if (vm.options.require.context !== 'sandbox') { | ||
@@ -76,3 +82,3 @@ try { | ||
| } | ||
| }; | ||
| } | ||
@@ -93,4 +99,6 @@ /** | ||
| // load as file | ||
| if (fs.existsSync(`${path}.js`)) return `${path}.js`; | ||
| for (var i = 0; i < vm.options.sourceExtensions.length; i++) { | ||
| var ext = vm.options.sourceExtensions[i]; | ||
| if (fs.existsSync(`${path}.${ext}`)) return `${path}.${ext}`; | ||
| } | ||
| if (fs.existsSync(`${path}.node`)) return `${path}.node`; | ||
@@ -112,3 +120,7 @@ if (fs.existsSync(`${path}.json`)) return `${path}.json`; | ||
| if (fs.existsSync(`${path}/index.js`)) return `${path}/index.js`; | ||
| for (var i = 0; i < vm.options.sourceExtensions.length; i++) { | ||
| var ext = vm.options.sourceExtensions[i]; | ||
| if (fs.existsSync(`${path}/index.${ext}`)) return `${path}/index.${ext}`; | ||
| } | ||
| if (fs.existsSync(`${path}/index.node`)) return `${path}/index.node`; | ||
@@ -260,3 +272,2 @@ | ||
| // lookup extensions | ||
| if (EXTENSIONS[extname]) { | ||
@@ -263,0 +274,0 @@ EXTENSIONS[extname](module, filename, dirname); |
+1
-1
@@ -16,3 +16,3 @@ { | ||
| ], | ||
| "version": "3.5.2", | ||
| "version": "3.6.0", | ||
| "main": "index.js", | ||
@@ -19,0 +19,0 @@ "repository": { |
+11
-3
@@ -96,3 +96,3 @@ # vm2 [![NPM Version][npm-image]][npm-url] [![NPM Downloads][downloads-image]][downloads-url] [![Package Quality][quality-image]][quality-url] [![Travis CI][travis-image]][travis-url] | ||
| **IMPORTANT**: Timeout is only effective on code you run through `run`. Timeout is NOT effective on any method returned by VM. | ||
| **IMPORTANT**: Timeout is only effective on synchronous code you run through `run`. Timeout is NOT effective on any method returned by VM. | ||
@@ -127,2 +127,3 @@ ```javascript | ||
| * `compiler` - `javascript` (default) or `coffeescript` or custom compiler function (which receives the code, and it's filepath). The library expects you to have coffee-script pre-installed if the compiler is set to `coffeescript`. | ||
| * `sourceExtensions` - Array of file extensions to treat as source code (default: `['js']`). | ||
| * `require` - `true` or object to enable `require` method (default: `false`). | ||
@@ -268,3 +269,3 @@ * `require.external` - `true` or an array of allowed external modules (default: `false`). | ||
| ## Protected objects (experimental) | ||
| ## Protected objects (experimental) | ||
@@ -327,2 +328,9 @@ Unlike `freeze`, this method allows sandboxed script to add/modify/delete properties on object with one exception - it is not possible to attach functions. Sandboxed script is therefore not able to modify methods like `toJSON`, `toString` or `inspect`. | ||
| ## Deployment | ||
| 1. Update the CHANGELOG | ||
| 2. Update the `package.json` version number | ||
| 3. Commit the changes | ||
| 4. Run `npm publish` | ||
| ## Sponsors | ||
@@ -334,3 +342,3 @@ | ||
| Copyright (c) 2014-2017 Patrik Simek | ||
| Copyright (c) 2014-2018 Patrik Simek | ||
@@ -337,0 +345,0 @@ The MIT License |
New author
Supply chain riskA new npm collaborator published a version of the package for the first time. New collaborators are usually benign additions to a project, but do indicate a change to the security surface area of a package.
Found 1 instance in 1 package
Uses eval
Supply chain riskPackage uses dynamic code execution (e.g., eval()), which is a dangerous practice. This can prevent the code from running in certain environments and increases the risk that the code may contain exploits or malicious behavior.
Found 1 instance in 1 package
Debug access
Supply chain riskUses debug, reflection and dynamic code execution features.
Found 2 instances in 1 package
Dynamic require
Supply chain riskDynamic require can indicate the package is performing dangerous or unsafe dynamic code execution.
Found 1 instance in 1 package
Filesystem access
Supply chain riskAccesses the file system, and could potentially read sensitive data.
Found 1 instance in 1 package
Long strings
Supply chain riskContains long string literals, which may be a sign of obfuscated or packed code.
Found 1 instance in 1 package
URL strings
Supply chain riskPackage contains fragments of external URLs or IP addresses, which the package may be accessing at runtime.
Found 1 instance in 1 package
Debug access
Supply chain riskUses debug, reflection and dynamic code execution features.
Found 1 instance in 1 package
Dynamic require
Supply chain riskDynamic require can indicate the package is performing dangerous or unsafe dynamic code execution.
Found 1 instance in 1 package
Filesystem access
Supply chain riskAccesses the file system, and could potentially read sensitive data.
Found 1 instance in 1 package
Long strings
Supply chain riskContains long string literals, which may be a sign of obfuscated or packed code.
Found 1 instance in 1 package
URL strings
Supply chain riskPackage contains fragments of external URLs or IP addresses, which the package may be accessing at runtime.
Found 1 instance in 1 package
85945
43.03%14
40%2074
53.63%356
2.3%29
81.25%2
Infinity%