isolated-vm
Advanced tools
Comparing version 4.3.2 to 4.3.3
@@ -7,3 +7,3 @@ { | ||
"command": "node-gyp", | ||
"args": [ "build" ], | ||
"args": [ "build", "-j", "8" ], | ||
"group": { | ||
@@ -10,0 +10,0 @@ "kind": "build", |
@@ -81,4 +81,4 @@ declare module "isolated-vm" { | ||
compileModule(code: string, scriptInfo?: ScriptInfo): Promise<Module>; | ||
compileModuleSync(code: string, scriptInfo?: ScriptInfo): Module; | ||
compileModule(code: string, options?: CompileModuleOptions): Promise<Module>; | ||
compileModuleSync(code: string, options?: CompileModuleOptions): Module; | ||
@@ -164,2 +164,11 @@ createContext(options?: ContextOptions): Promise<Context>; | ||
export type CompileModuleOptions = ScriptInfo & { | ||
/** | ||
* Callback which will be invoked the first time this module accesses `import.meta`. The `meta` | ||
* object will be passed as the first argument. This option may only be used when invoking | ||
* `compileModule` from within the same isolate. | ||
*/ | ||
meta?: (meta: any) => void; | ||
} | ||
/** | ||
@@ -166,0 +175,0 @@ * A context is a sandboxed execution environment within an isolate. Each context contains its own |
{ | ||
"name": "isolated-vm", | ||
"version": "4.3.2", | ||
"version": "4.3.3", | ||
"description": "Access to multiple isolates", | ||
@@ -5,0 +5,0 @@ "main": "isolated-vm.js", |
154
README.md
[![npm version](https://badgen.now.sh/npm/v/isolated-vm)](https://www.npmjs.com/package/isolated-vm) | ||
[![isc license](https://badgen.now.sh/npm/license/isolated-vm)](https://github.com/laverdet/isolated-vm/blob/main/LICENSE) | ||
[![travis build](https://badgen.now.sh/travis/laverdet/isolated-vm/main)](https://travis-ci.org/laverdet/isolated-vm) | ||
[![travis build](https://badgen.now.sh/travis/laverdet/isolated-vm/main)](https://travis-ci.com/laverdet/isolated-vm) | ||
[![npm downloads](https://badgen.now.sh/npm/dm/isolated-vm)](https://www.npmjs.com/package/isolated-vm) | ||
@@ -12,9 +12,9 @@ | ||
`isolated-vm` is a library for nodejs which gives you access to v8's `Isolate` interface. This | ||
allows you to create JavaScript environments which are completely *isolated* from each other. You | ||
might find this module useful if you need to run some untrusted code in a secure way. You may also | ||
find this module useful if you need to run some JavaScript simultaneously in multiple threads. You | ||
may find this project *very* useful if you need to do both at the same time! | ||
allows you to create JavaScript environments which are completely *isolated* from each other. This | ||
can be a powerful tool to run code in a fresh JavaScript environment completely free of extraneous | ||
capabilities provided by the nodejs runtime. | ||
* [Requirements](#requirements) | ||
* [Who Is Using isolated-vm](#who-is-using-isolated-vm) | ||
* [Security](#security) | ||
* [API Documentation](#api-documentation) | ||
@@ -40,3 +40,3 @@ * [Isolate](#class-isolate-transferable) | ||
* Windows + OS X users should follow the instuctions here: [node-gyp](https://github.com/nodejs/node-gyp) | ||
* Windows + OS X users should follow the instructions here: [node-gyp](https://github.com/nodejs/node-gyp) | ||
* Ubuntu users should run: `sudo apt-get install python g++ build-essential` | ||
@@ -47,2 +47,3 @@ * Alpine users should run: `sudo apk add python make g++` | ||
WHO IS USING ISOLATED-VM | ||
@@ -52,3 +53,3 @@ ------------------------ | ||
* [Screeps](https://screeps.com/) - Screeps is an online JavaScript-based MMO+RPG game. They are | ||
using isolated-vm to run arbitrary player-supplied code in secure enviroments which can persistent | ||
using isolated-vm to run arbitrary player-supplied code in secure environments which can persistent | ||
for several days at a time. | ||
@@ -67,2 +68,43 @@ | ||
SECURITY | ||
-------- | ||
Running untrusted code is an extraordinarily difficult problem which must be approached with great | ||
care. Use of `isolated-vm` to run untrusted code does not automatically make your application safe. | ||
Through carelessness or misuse of the library it can be possible to leak sensitive data or grant | ||
undesired privileges to an isolate. | ||
At a minimum you should take care not to leak any instances of `isolated-vm` objects (`Reference`, | ||
`ExternalCopy`, etc) to untrusted code. It is usually trivial for an attacker to use these instances | ||
as a springboard back into the nodejs isolate which will yield complete control over a process. | ||
Additionally, it is wise to keep nodejs up to date through point releases which affect v8. You can | ||
find these on the [nodejs changelog](https://github.com/nodejs/node/blob/master/CHANGELOG.md) by | ||
looking for entries such as "update V8 to 9.1.269.36 (Michaël Zasso) #38273". Historically there | ||
have usually been 3-5 of these updates within a single nodejs LTS release cycle. It is *not* | ||
recommended to use odd-numbered nodejs releases since these frequently break ABI and API | ||
compatibility and isolated-vm doesn't aim to be compatible with bleeding edge v8. | ||
Against potentially hostile code you should also consider turning on [v8 untrusted code | ||
mitigations](https://v8.dev/docs/untrusted-code-mitigations), which helps address the class of | ||
speculative execution attacks known as Spectre and Meltdown. You can enable this feature by running | ||
`node` with the `--untrusted-code-mitigations` flag. This feature comes with a slight performance | ||
cost and must be enabled per-process, therefore nodejs disables it by default. | ||
v8 is a relatively robust runtime, but there are always new and exciting ways to crash, hang, | ||
exploit, or otherwise disrupt a process with plain old JavaScript. Your application must be | ||
resilient to these kinds of issues and attacks. It's a good idea to keep instances of `isolated-vm` | ||
in a different nodejs process than other critical infrastructure. | ||
If [advanced persistent threats](https://en.wikipedia.org/wiki/Advanced_persistent_threat) are | ||
within your threat model it's a very good idea to architect your application using a foundation | ||
similar to Chromium's [site | ||
isolation](https://www.chromium.org/Home/chromium-security/site-isolation). You'll also need to make | ||
sure to keep your system kernel up to date against [local privilege | ||
escalation](https://en.wikipedia.org/wiki/Privilege_escalation) attacks. Running your service in a | ||
container such as a Docker may be a good idea but it is important to research container escape | ||
attacks as well. | ||
API DOCUMENTATION | ||
@@ -160,2 +202,5 @@ ----------------- | ||
* `options` *[object]* | ||
* `meta` *[function]* - Callback which will be invoked the first time this module accesses | ||
`import.meta`. The `meta` object will be passed as the first argument. This option may only be | ||
used when invoking `compileModule` from within the same isolate. | ||
* [`{ ...CachedDataOptions }`](#cacheddataoptions) | ||
@@ -567,35 +612,2 @@ * [`{ ...ScriptOrigin }`](#scriptorigin) | ||
SECURITY | ||
-------- | ||
Use of `isolated-vm` to run untrusted code does not automatically make your application safe. | ||
Through carelessness or misuse of the library it can be possible to leak sensitive data or grant | ||
undesired privileges to an isolate. | ||
At a minimum you should take care not to leak any instances of `isolated-vm` objects (`Reference`, | ||
`ExternalCopy`, etc) to untrusted code. It is usually trivial for an attacker to use these instances | ||
as a springboard back into the nodejs isolate which will yield complete control over a process. | ||
Simply wrapping these instances in closures is usually enough to keep the internal objects safe. An | ||
example of a safe logging function follows: | ||
```js | ||
context.evalClosureSync( | ||
`globalThis.log = (...args) => | ||
$0.applyIgnored(undefined, args, { arguments: { copy: true } });`, | ||
[ (...args) => console.log(...args) ], | ||
{ arguments: { reference: true } }); | ||
``` | ||
Against potentially hostile code you should also consider turning on [v8 untrusted code | ||
mitigations](https://v8.dev/docs/untrusted-code-mitigations), which addresses the class of | ||
speculative execution attacks known as Spectre and Meltdown. You can enable this feature by running | ||
`node` with the `--untrusted-code-mitigations` flag. This feature comes with a slight performance | ||
cost and must be enabled per-process, therefore nodejs disables it by default. | ||
v8 is a relatively robust runtime, but there are always new and exciting ways to crash, hang, or | ||
otherwise disrupt a process with plain old JavaScript. Your application must be resilient to these | ||
kinds of issues. It's a good idea to keep instances of `isolated-vm` in a different nodejs process | ||
than other critical infrastructure. | ||
EXAMPLES | ||
@@ -656,69 +668,9 @@ -------- | ||
Another example which shows how calls to the asynchronous methods will execute in separate threads | ||
providing you with parallelism. Note that each isolate only "owns" a thread while it is executing. | ||
So you could have hundreds of isolates sitting idle and they would not be using a thread. | ||
```js | ||
// A simple function to sum a range of numbers. This can also be expressed as: | ||
// (max * (max - 1) - min * (min - 1)) / 2 | ||
// But this is an easy way to show off the async features of the module. | ||
function sum(min, max) { | ||
let sum = 0; | ||
for (let ii = min; ii < max; ++ii) { | ||
sum += ii; | ||
} | ||
return sum; | ||
} | ||
// I chose this number because it's big but also small enough that we don't go past JS's integer | ||
// limit. | ||
let num = Math.pow(2, 27); | ||
// First we execute a single thread run | ||
let start1 = new Date; | ||
let result = sum(0, num); | ||
console.log('Calculated '+ result+ ' in '+ (Date.now() - start1)+ 'ms'); | ||
// Now we do the same thing over 4 threads | ||
let start2 = new Date; | ||
let ivm = require('isolated-vm'); | ||
let numThreads = 4; | ||
let promises = Array(numThreads).fill().map(async function(_, ii) { | ||
// Set up 4 isolates with the `sum` function from above | ||
let isolate = new ivm.Isolate(); | ||
let context = await isolate.createContext(); | ||
let script = await isolate.compileScript(sum+ ''); | ||
await script.run(context); | ||
let fnReference = await context.global.get('sum'); | ||
// Run one slice of the sum loop | ||
let min = Math.floor(num / numThreads * ii); | ||
let max = Math.floor(num / numThreads * (ii + 1)); | ||
return await fnReference.apply(undefined, [ min, max ]); | ||
}); | ||
Promise.all(promises).then(function(sums) { | ||
let result = sums.reduce((a, b) => a + b, 0); | ||
console.log('Calculated '+ result+ ' in '+ (Date.now() - start2)+ 'ms'); | ||
}); | ||
// They get the same answer but the async version can do it much faster! Even | ||
// with the overhead of building 4 isolates | ||
// > Calculated 9007199187632128 in 1485ms | ||
// > Calculated 9007199187632128 in 439ms | ||
``` | ||
Included in the repository is an example of how you can write quicksort using a SharedArrayBuffer to | ||
sort over multiple threads. See: [parallel-sort-example.js](https://github.com/laverdet/isolated-vm/blob/main/parallel-sort-example.js). | ||
ALTERNATIVES | ||
------------ | ||
The primary goal of isolated-vm is to create a powerful and secure environment for running untrusted | ||
JavaScript code. isolated-vm is also a good way to build single-process multithreaded JavaScript | ||
applications, though if parallelism of trusted code is your only goal then there are probably better | ||
options out there. | ||
Below is a quick summary of some other options available on nodejs and how they differ from | ||
isolated-vm. The table headers are defined as follows: | ||
* **Secure**: Safely run untrusted code | ||
* **Secure**: Obstructs access to unsafe nodejs capabilities | ||
* **Memory Limits**: Possible to set memory limits / safe against heap overflow DoS attacks | ||
@@ -725,0 +677,0 @@ * **Isolated**: Is garbage collection, heap, etc isolated from application |
'use strict'; | ||
let ivm = require('isolated-vm'); | ||
let isolate = new ivm.Isolate; | ||
(async function() { | ||
let script = await isolate.compileScript('for(;;);'); | ||
let context = await isolate.createContext(); | ||
setTimeout(function() { | ||
isolate.dispose(); | ||
}, 1); | ||
try { | ||
await script.run(context); | ||
} catch (err) { | ||
// if this fails the process just locks up | ||
console.log('pass'); | ||
for (let ii = 0; ii < 100; ++ii) { | ||
let isolate = new ivm.Isolate; | ||
let script = await isolate.compileScript('for(;;);'); | ||
let context = await isolate.createContext(); | ||
setTimeout(function() { | ||
isolate.dispose(); | ||
}, 1); | ||
try { | ||
await script.run(context); | ||
} catch (err) { | ||
// if this fails the process just locks up | ||
} | ||
} | ||
console.log('pass'); | ||
}()).catch(console.error); |
@@ -0,1 +1,3 @@ | ||
// node-args: --expose-gc | ||
const ivm = require('isolated-vm'); | ||
@@ -35,2 +37,12 @@ const assert = require('assert'); | ||
await assert.doesNotReject(async () => { | ||
const isolate = new ivm.Isolate; | ||
for (let i = 0; i < 20; i++) { | ||
const context = await isolate.createContext(); | ||
await context.eval('const value = new Uint8Array(1024 * 1024 * 16); Promise.reject();').catch(() => {}); | ||
context.release(); | ||
global.gc(); | ||
} | ||
}); | ||
try { | ||
@@ -37,0 +49,0 @@ context.evalSync(` |
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
166
546767
2889
681