UNPKG

14.1 kBMarkdownView Raw
1# 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] [![Known Vulnerabilities][snyk-image]][snyk-url]
2
3vm2 is a sandbox that can run untrusted code with whitelisted Node's built-in modules. Securely!
4
5**Features**
6
7* Runs untrusted code securely in a single process with your code side by side
8* Full control over sandbox's console output
9* Sandbox has limited access to process's methods
10* Sandbox can require modules (builtin and external)
11* You can limit access to certain (or all) builtin modules
12* You can securely call methods and exchange data and callbacks between sandboxes
13* Is immune to all known methods of attacks
14* Transpilers support
15
16**How does it work**
17
18* It uses internal VM module to create secure context
19* It uses [Proxies](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Proxy) to prevent escaping the sandbox
20* It overrides builtin require to control access to modules
21
22**What is the difference between Node's vm and vm2?**
23
24Try it yourself:
25
26```javascript
27const vm = require('vm');
28vm.runInNewContext('this.constructor.constructor("return process")().exit()');
29console.log('Never gets executed.');
30```
31
32```javascript
33const {VM} = require('vm2');
34new VM().run('this.constructor.constructor("return process")().exit()');
35// Throws ReferenceError: process is not defined
36```
37
38## Installation
39
40**IMPORTANT**: Requires Node.js 6 or newer.
41
42 npm install vm2
43
44## Quick Example
45
46```javascript
47const {VM} = require('vm2');
48const vm = new VM();
49
50vm.run(`process.exit()`); // TypeError: process.exit is not a function
51```
52
53```javascript
54const {NodeVM} = require('vm2');
55const vm = new NodeVM({
56 require: {
57 external: true
58 }
59});
60
61vm.run(`
62 var request = require('request');
63 request('http://www.google.com', function (error, response, body) {
64 console.error(error);
65 if (!error && response.statusCode == 200) {
66 console.log(body) // Show the HTML for the Google homepage.
67 }
68 })
69`, 'vm.js');
70```
71
72## Documentation
73
74* [VM](#vm)
75* [NodeVM](#nodevm)
76* [VMScript](#vmscript)
77* [Error handling](#error-handling)
78* [Debugging a sandboxed code](#debugging-a-sandboxed-code)
79* [Read-only objects](#read-only-objects-experimental)
80* [Protected objects](#protected-objects-experimental)
81* [Cross-sandbox relationships](#cross-sandbox-relationships)
82* [CLI](#cli)
83* [2.x to 3.x changes](https://github.com/patriksimek/vm2/wiki/2.x-to-3.x-changes)
84* [1.x and 2.x docs](https://github.com/patriksimek/vm2/wiki/1.x-and-2.x-docs)
85* [Contributing](https://github.com/patriksimek/vm2/wiki/Contributing)
86
87## VM
88
89VM is a simple sandbox, without `require` feature, to synchronously run an untrusted code. Only JavaScript built-in objects + Buffer are available. Scheduling functions (`setInterval`, `setTimeout` and `setImmediate`) are not available by default.
90
91**Options:**
92
93* `timeout` - Script timeout in milliseconds.
94* `sandbox` - VM's global object.
95* `compiler` - `javascript` (default) or `coffeescript` or custom compiler function. The library expects you to have coffee-script pre-installed if the compiler is set to `coffeescript`.
96* `eval` - If set to `false` any calls to `eval` or function constructors (`Function`, `GeneratorFunction`, etc) will throw an `EvalError` (default: `true`).
97* `wasm` - If set to `false` any attempt to compile a WebAssembly module will throw a `WebAssembly.CompileError` (default: `true`).
98* `fixAsync` - If set to `true` any attempt to run code using async will throw a `VMError` (default: `false`).
99
100**IMPORTANT**: Timeout is only effective on synchronous code you run through `run`. Timeout is NOT effective on any method returned by VM. There're some situations when timeout doesn't work - see [#244](https://github.com/patriksimek/vm2/pull/244).
101
102```javascript
103const {VM} = require('vm2');
104
105const vm = new VM({
106 timeout: 1000,
107 sandbox: {}
108});
109
110vm.run("process.exit()"); // throws ReferenceError: process is not defined
111```
112
113You can also retrieve values from VM.
114
115```javascript
116let number = vm.run("1337"); // returns 1337
117```
118
119**TIP**: See tests for more usage examples.
120
121## NodeVM
122
123Unlike `VM`, `NodeVM` lets you require modules same way like in regular Node's context.
124
125**Options:**
126
127* `console` - `inherit` to enable console, `redirect` to redirect to events, `off` to disable console (default: `inherit`).
128* `sandbox` - VM's global object.
129* `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`.
130* `eval` - If set to `false` any calls to `eval` or function constructors (`Function`, `GeneratorFunction`, etc) will throw an `EvalError` (default: `true`).
131* `wasm` - If set to `false` any attempt to compile a WebAssembly module will throw a `WebAssembly.CompileError` (default: `true`).
132* `sourceExtensions` - Array of file extensions to treat as source code (default: `['js']`).
133* `require` - `true` or object to enable `require` method (default: `false`).
134* `require.external` - `true`, an array of allowed external modules or an object (default: `false`).
135* `require.external.modules` - Array of allowed external modules. Also supports wildcards, so specifying `['@scope/*-ver-??]`, for instance, will allow using all modules having a name of the form `@scope/something-ver-aa`, `@scope/other-ver-11`, etc.
136* `require.external.transitive` - Boolean which indicates if transitive dependencies of external modules are allowed (default: `false`).
137* `require.builtin` - Array of allowed builtin modules, accepts ["*"] for all (default: none).
138* `require.root` - Restricted path(s) where local modules can be required (default: every path).
139* `require.mock` - Collection of mock modules (both external or builtin).
140* `require.context` - `host` (default) to require modules in host and proxy them to sandbox. `sandbox` to load, compile and require modules in sandbox. Builtin modules except `events` always required in host and proxied to sandbox.
141* `require.import` - Array of modules to be loaded into NodeVM on start.
142* `require.resolve` - An additional lookup function in case a module wasn't found in one of the traditional node lookup paths.
143* `nesting` - `true` to enable VMs nesting (default: `false`).
144* `wrapper` - `commonjs` (default) to wrap script into CommonJS wrapper, `none` to retrieve value returned by the script.
145* `argv` - Array to be passed to `process.argv`.
146* `env` - Object to be passed to `process.env`.
147
148**IMPORTANT**: Timeout is not effective for NodeVM so it is not immune to `while (true) {}` or similar evil.
149
150**REMEMBER**: The more modules you allow, the more fragile your sandbox becomes.
151
152```javascript
153const {NodeVM} = require('vm2');
154
155const vm = new NodeVM({
156 console: 'inherit',
157 sandbox: {},
158 require: {
159 external: true,
160 builtin: ['fs', 'path'],
161 root: "./",
162 mock: {
163 fs: {
164 readFileSync() { return 'Nice try!'; }
165 }
166 }
167 }
168});
169
170// Sync
171
172let functionInSandbox = vm.run("module.exports = function(who) { console.log('hello '+ who); }");
173functionInSandbox('world');
174
175// Async
176
177let functionWithCallbackInSandbox = vm.run("module.exports = function(who, callback) { callback('hello '+ who); }");
178functionWithCallbackInSandbox('world', (greeting) => {
179 console.log(greeting);
180});
181```
182
183When `wrapper` is set to `none`, `NodeVM` behaves more like `VM` for synchronous code.
184
185```javascript
186assert.ok(vm.run('return true') === true);
187```
188
189**TIP**: See tests for more usage examples.
190
191### Loading modules by relative path
192
193To load modules by relative path, you must pass full path of the script you're running as a second argument of vm's `run` method. Filename then also shows up in any stack traces produced from the script.
194
195```javascript
196vm.run("require('foobar')", "/data/myvmscript.js");
197```
198
199## VMScript
200
201You can increase performance by using pre-compiled scripts. The pre-compiled VMScript can be run later multiple times. It is important to note that the code is not bound to any VM (context); rather, it is bound before each run, just for that run.
202
203```javascript
204const {VM, VMScript} = require('vm2');
205
206const vm = new VM();
207const script = new VMScript("Math.random()");
208console.log(vm.run(script));
209console.log(vm.run(script));
210```
211
212Works for both `VM` and `NodeVM`.
213
214```javascript
215const {NodeVM, VMScript} = require('vm2');
216
217const vm = new NodeVM();
218const script = new VMScript("module.exports = Math.random()");
219console.log(vm.run(script));
220console.log(vm.run(script));
221```
222
223Code is compiled automatically first time you run it. You can compile the code anytime with `script.compile()`. Once the code is compiled, the method has no effect.
224
225## Error handling
226
227Errors in code compilation and synchronous code execution can be handled by `try`/`catch`. Errors in asynchronous code execution can be handled by attaching `uncaughtException` event handler to Node's `process`.
228
229```javascript
230try {
231 var script = new VMScript("Math.random()").compile();
232} catch (err) {
233 console.error('Failed to compile script.', err);
234}
235
236try {
237 vm.run(script);
238} catch (err) {
239 console.error('Failed to execute script.', err);
240}
241
242process.on('uncaughtException', (err) => {
243 console.error('Asynchronous error caught.', err);
244})
245```
246
247## Debugging a sandboxed code
248
249You can debug/inspect code running in the sandbox as if it was running in a normal process.
250
251- You can use breakpoints (requires you to specify a script file name)
252- You can use `debugger` keyword.
253- You can use step-in to step inside the code running in the sandbox.
254
255**Example**
256
257/tmp/main.js:
258```javascript
259const {VM, VMScript} = require('.');
260const fs = require('fs');
261const file = `${__dirname}/sandbox.js`;
262
263// By providing a file name as second argument you enable breakpoints
264const script = new VMScript(fs.readFileSync(file), file);
265
266new VM().run(script);
267```
268
269/tmp/sandbox.js
270```javascript
271const foo = 'ahoj';
272
273// Debugger keyword works just fine anywhere.
274// Even without specifying a file name to the VMScript object.
275debugger;
276```
277
278## Read-only objects (experimental)
279
280To prevent sandboxed script to add/change/delete properties to/from the proxied objects, you can use `freeze` methods to make the object read-only. This is only effective inside VM. Frozen objects are affected deeply. Primitive types can not be frozen.
281
282**Example without using `freeze`:**
283
284```javascript
285const util = {
286 add: (a, b) => a + b
287}
288
289const vm = new VM({
290 sandbox: {util}
291});
292
293vm.run('util.add = (a, b) => a - b');
294console.log(util.add(1, 1)); // returns 0
295```
296
297**Example with using `freeze`:**
298
299```javascript
300const vm = new VM(); // Objects specified in sandbox can not be frozen.
301vm.freeze(util, 'util'); // Second argument adds object to global.
302
303vm.run('util.add = (a, b) => a - b'); // Fails silently when not in strict mode.
304console.log(util.add(1, 1)); // returns 2
305```
306
307**IMPORTANT:** It is not possible to freeze objects that has already been proxied to the VM.
308
309## Protected objects (experimental)
310
311Unlike `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`.
312
313**IMPORTANT:** It is not possible to protect objects that has already been proxied to the VM.
314
315## Cross-sandbox relationships
316
317```javascript
318const assert = require('assert');
319const {VM} = require('vm2');
320
321const sandbox = {
322 object: new Object(),
323 func: new Function(),
324 buffer: new Buffer([0x01, 0x05])
325}
326
327const vm = new VM({sandbox});
328
329assert.ok(vm.run(`object`) === sandbox.object);
330assert.ok(vm.run(`object instanceof Object`));
331assert.ok(vm.run(`object`) instanceof Object);
332assert.ok(vm.run(`object.__proto__ === Object.prototype`));
333assert.ok(vm.run(`object`).__proto__ === Object.prototype);
334
335assert.ok(vm.run(`func`) === sandbox.func);
336assert.ok(vm.run(`func instanceof Function`));
337assert.ok(vm.run(`func`) instanceof Function);
338assert.ok(vm.run(`func.__proto__ === Function.prototype`));
339assert.ok(vm.run(`func`).__proto__ === Function.prototype);
340
341assert.ok(vm.run(`new func() instanceof func`));
342assert.ok(vm.run(`new func()`) instanceof sandbox.func);
343assert.ok(vm.run(`new func().__proto__ === func.prototype`));
344assert.ok(vm.run(`new func()`).__proto__ === sandbox.func.prototype);
345
346assert.ok(vm.run(`buffer`) === sandbox.buffer);
347assert.ok(vm.run(`buffer instanceof Buffer`));
348assert.ok(vm.run(`buffer`) instanceof Buffer);
349assert.ok(vm.run(`buffer.__proto__ === Buffer.prototype`));
350assert.ok(vm.run(`buffer`).__proto__ === Buffer.prototype);
351assert.ok(vm.run(`buffer.slice(0, 1) instanceof Buffer`));
352assert.ok(vm.run(`buffer.slice(0, 1)`) instanceof Buffer);
353```
354
355## CLI
356
357Before you can use vm2 in command line, install it globally with `npm install vm2 -g`.
358
359```
360$ vm2 ./script.js
361```
362
363## Known Issues
364
365* It is not possible to define class that extends proxied class.
366
367## Deployment
368
3691. Update the CHANGELOG
3702. Update the `package.json` version number
3713. Commit the changes
3724. Run `npm publish`
373
374## Sponsors
375
376[![Integromat][integromat-image]][integromat-url]
377
378[npm-image]: https://img.shields.io/npm/v/vm2.svg?style=flat-square
379[npm-url]: https://www.npmjs.com/package/vm2
380[downloads-image]: https://img.shields.io/npm/dm/vm2.svg?style=flat-square
381[downloads-url]: https://www.npmjs.com/package/vm2
382[quality-image]: http://npm.packagequality.com/shield/vm2.svg?style=flat-square
383[quality-url]: http://packagequality.com/#?package=vm2
384[travis-image]: https://img.shields.io/travis/patriksimek/vm2/master.svg?style=flat-square&label=unit
385[travis-url]: https://travis-ci.org/patriksimek/vm2
386[snyk-image]: https://snyk.io/test/github/patriksimek/vm2/badge.svg
387[snyk-url]: https://snyk.io/test/github/patriksimek/vm2
388[integromat-image]: https://static.integromat.com/logo/45_text.png
389[integromat-url]: https://www.integromat.com