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 |
|
3 | vm2 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 |
|
24 | Try it yourself:
|
25 |
|
26 | ```javascript
|
27 | const vm = require('vm');
|
28 | vm.runInNewContext('this.constructor.constructor("return process")().exit()');
|
29 | console.log('Never gets executed.');
|
30 | ```
|
31 |
|
32 | ```javascript
|
33 | const {VM} = require('vm2');
|
34 | new 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
|
47 | const {VM} = require('vm2');
|
48 | const vm = new VM();
|
49 |
|
50 | vm.run(`process.exit()`); // TypeError: process.exit is not a function
|
51 | ```
|
52 |
|
53 | ```javascript
|
54 | const {NodeVM} = require('vm2');
|
55 | const vm = new NodeVM({
|
56 | require: {
|
57 | external: true
|
58 | }
|
59 | });
|
60 |
|
61 | vm.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 |
|
89 | VM 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
|
103 | const {VM} = require('vm2');
|
104 |
|
105 | const vm = new VM({
|
106 | timeout: 1000,
|
107 | sandbox: {}
|
108 | });
|
109 |
|
110 | vm.run("process.exit()"); // throws ReferenceError: process is not defined
|
111 | ```
|
112 |
|
113 | You can also retrieve values from VM.
|
114 |
|
115 | ```javascript
|
116 | let number = vm.run("1337"); // returns 1337
|
117 | ```
|
118 |
|
119 | **TIP**: See tests for more usage examples.
|
120 |
|
121 | ## NodeVM
|
122 |
|
123 | Unlike `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
|
153 | const {NodeVM} = require('vm2');
|
154 |
|
155 | const 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 |
|
172 | let functionInSandbox = vm.run("module.exports = function(who) { console.log('hello '+ who); }");
|
173 | functionInSandbox('world');
|
174 |
|
175 | // Async
|
176 |
|
177 | let functionWithCallbackInSandbox = vm.run("module.exports = function(who, callback) { callback('hello '+ who); }");
|
178 | functionWithCallbackInSandbox('world', (greeting) => {
|
179 | console.log(greeting);
|
180 | });
|
181 | ```
|
182 |
|
183 | When `wrapper` is set to `none`, `NodeVM` behaves more like `VM` for synchronous code.
|
184 |
|
185 | ```javascript
|
186 | assert.ok(vm.run('return true') === true);
|
187 | ```
|
188 |
|
189 | **TIP**: See tests for more usage examples.
|
190 |
|
191 | ### Loading modules by relative path
|
192 |
|
193 | To 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
|
196 | vm.run("require('foobar')", "/data/myvmscript.js");
|
197 | ```
|
198 |
|
199 | ## VMScript
|
200 |
|
201 | You 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
|
204 | const {VM, VMScript} = require('vm2');
|
205 |
|
206 | const vm = new VM();
|
207 | const script = new VMScript("Math.random()");
|
208 | console.log(vm.run(script));
|
209 | console.log(vm.run(script));
|
210 | ```
|
211 |
|
212 | Works for both `VM` and `NodeVM`.
|
213 |
|
214 | ```javascript
|
215 | const {NodeVM, VMScript} = require('vm2');
|
216 |
|
217 | const vm = new NodeVM();
|
218 | const script = new VMScript("module.exports = Math.random()");
|
219 | console.log(vm.run(script));
|
220 | console.log(vm.run(script));
|
221 | ```
|
222 |
|
223 | Code 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 |
|
227 | Errors 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
|
230 | try {
|
231 | var script = new VMScript("Math.random()").compile();
|
232 | } catch (err) {
|
233 | console.error('Failed to compile script.', err);
|
234 | }
|
235 |
|
236 | try {
|
237 | vm.run(script);
|
238 | } catch (err) {
|
239 | console.error('Failed to execute script.', err);
|
240 | }
|
241 |
|
242 | process.on('uncaughtException', (err) => {
|
243 | console.error('Asynchronous error caught.', err);
|
244 | })
|
245 | ```
|
246 |
|
247 | ## Debugging a sandboxed code
|
248 |
|
249 | You 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
|
259 | const {VM, VMScript} = require('.');
|
260 | const fs = require('fs');
|
261 | const file = `${__dirname}/sandbox.js`;
|
262 |
|
263 | // By providing a file name as second argument you enable breakpoints
|
264 | const script = new VMScript(fs.readFileSync(file), file);
|
265 |
|
266 | new VM().run(script);
|
267 | ```
|
268 |
|
269 | /tmp/sandbox.js
|
270 | ```javascript
|
271 | const foo = 'ahoj';
|
272 |
|
273 | // Debugger keyword works just fine anywhere.
|
274 | // Even without specifying a file name to the VMScript object.
|
275 | debugger;
|
276 | ```
|
277 |
|
278 | ## Read-only objects (experimental)
|
279 |
|
280 | To 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
|
285 | const util = {
|
286 | add: (a, b) => a + b
|
287 | }
|
288 |
|
289 | const vm = new VM({
|
290 | sandbox: {util}
|
291 | });
|
292 |
|
293 | vm.run('util.add = (a, b) => a - b');
|
294 | console.log(util.add(1, 1)); // returns 0
|
295 | ```
|
296 |
|
297 | **Example with using `freeze`:**
|
298 |
|
299 | ```javascript
|
300 | const vm = new VM(); // Objects specified in sandbox can not be frozen.
|
301 | vm.freeze(util, 'util'); // Second argument adds object to global.
|
302 |
|
303 | vm.run('util.add = (a, b) => a - b'); // Fails silently when not in strict mode.
|
304 | console.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 |
|
311 | 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`.
|
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
|
318 | const assert = require('assert');
|
319 | const {VM} = require('vm2');
|
320 |
|
321 | const sandbox = {
|
322 | object: new Object(),
|
323 | func: new Function(),
|
324 | buffer: new Buffer([0x01, 0x05])
|
325 | }
|
326 |
|
327 | const vm = new VM({sandbox});
|
328 |
|
329 | assert.ok(vm.run(`object`) === sandbox.object);
|
330 | assert.ok(vm.run(`object instanceof Object`));
|
331 | assert.ok(vm.run(`object`) instanceof Object);
|
332 | assert.ok(vm.run(`object.__proto__ === Object.prototype`));
|
333 | assert.ok(vm.run(`object`).__proto__ === Object.prototype);
|
334 |
|
335 | assert.ok(vm.run(`func`) === sandbox.func);
|
336 | assert.ok(vm.run(`func instanceof Function`));
|
337 | assert.ok(vm.run(`func`) instanceof Function);
|
338 | assert.ok(vm.run(`func.__proto__ === Function.prototype`));
|
339 | assert.ok(vm.run(`func`).__proto__ === Function.prototype);
|
340 |
|
341 | assert.ok(vm.run(`new func() instanceof func`));
|
342 | assert.ok(vm.run(`new func()`) instanceof sandbox.func);
|
343 | assert.ok(vm.run(`new func().__proto__ === func.prototype`));
|
344 | assert.ok(vm.run(`new func()`).__proto__ === sandbox.func.prototype);
|
345 |
|
346 | assert.ok(vm.run(`buffer`) === sandbox.buffer);
|
347 | assert.ok(vm.run(`buffer instanceof Buffer`));
|
348 | assert.ok(vm.run(`buffer`) instanceof Buffer);
|
349 | assert.ok(vm.run(`buffer.__proto__ === Buffer.prototype`));
|
350 | assert.ok(vm.run(`buffer`).__proto__ === Buffer.prototype);
|
351 | assert.ok(vm.run(`buffer.slice(0, 1) instanceof Buffer`));
|
352 | assert.ok(vm.run(`buffer.slice(0, 1)`) instanceof Buffer);
|
353 | ```
|
354 |
|
355 | ## CLI
|
356 |
|
357 | Before 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 |
|
369 | 1. Update the CHANGELOG
|
370 | 2. Update the `package.json` version number
|
371 | 3. Commit the changes
|
372 | 4. 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
|