Secure & isolated JS environments for nodejs
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. This
can be a powerful tool to run code in a fresh JavaScript environment completely free of extraneous
capabilities provided by the nodejs runtime.
The version of isolated-vm you should be using depends on your version of nodejs.
nodejs version isolated-vm version 22.x 5.x or 4.x 24.x 6.x or 5.x 26.x 7.xYou may ask "what about odd-numbered versions of nodejs" and the answer is that we can't support it at this time.
isolated-vm is currently in maintenance mode. It will continue to be supported for as long as is
technically feasible. In the
experimental branch I have been working
on a new version for quite some time. It is worth taking a look if you are interested in this
project, but it is certainly not ready for serious applications.
Please sponsor this project if you feel like it.
This project requires nodejs version 16.x (or later).
If you are using a version of nodejs 20.x or later, you must pass --no-node-snapshot to node.
Furthermore, to install this module you will need a compiler installed. If you run into errors while
running npm install isolated-vm it is likely you don't have a compiler set up, or your compiler is
too old.
sudo apt-get install python g++ build-essentialsudo apk add python3 make g++sudo yum install gcc72 gcc72-c++sudo pacman -S make gcc pythonsudo dnf install python3 make gcc gcc-c++ zlib-devel brotli-devel openssl-develScreeps - Screeps is an online JavaScript-based MMO+RPG game. They are using isolated-vm to run arbitrary player-supplied code in secure environments which can persistent for several days at a time.
Fly - Fly is a programmable CDN which hosts dynamic endpoints as opposed to just static resources. They are using isolated-vm to run globally distributed applications, where each application may have wildly different traffic patterns.
Algolia - Algolia is a Search as a Service provider. They use
isolated-vm to power their Custom Crawler product,
which allows them to safely execute user-provided code for content extraction.
Tripadvisor - Tripadvisor is the world’s largest travel platform.
They use isolated-vm to server-side render thousands of React pages per second.
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 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.
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 are within your threat model it's a very good idea to architect your application using a foundation similar to Chromium's site isolation. You'll also need to make sure to keep your system kernel up to date against local 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.
Since isolates share no resources with each other, most of this API is built to provide primitives
which make marshalling data between many isolates quick and easy. The only way to pass data from one
isolate to another is to first make that data transferable. Primitives (except for Symbol) are
always transferable. This means if you invoke a function in a different isolate with a number or
string as the argument, it will work fine. If you need to pass more complex information you will
have to first make the data transferable with one of the methods here.
Most methods will provide both a synchronous and an asynchronous version. Calling the synchronous functions will block your thread while the method runs and eventually returns a value. The asynchronous functions will return a Promise while the work runs in a separate thread pool.
There are some rules about which functions may be called from certain contexts:
Additionally, some methods will provide an "ignored" version which runs asynchronously but returns no promise. This can be a good option when the calling isolate would ignore the promise anyway, since the ignored versions can skip an extra thread synchronization. Just be careful because this swallows any thrown exceptions which might make problems hard to track down.
It's also worth noting that all asynchronous invocations will run in the order they were queued,
regardless of whether or not you wait on them. So, for instance, you could call several "ignored"
methods in a row and then await on a final async method to observe some side-effect of the
ignored methods.
Isolate [transferable]This is the main reference to an isolate. Every handle to an isolate is transferable, which means
you can give isolates references to each other. An isolate will remain valid as long as someone
holds a handle to the isolate or anything created inside that isolate. Once an isolate is lost the
garbage collector should eventually find it and clean up its memory. Since an isolate and all it
contains can represent quite a large chunk of memory though you may want to explicitly call the
dispose() method on isolates that you are finished with to get that memory back immediately.
new ivm.Isolate(options)
options [object]memoryLimit [number] - Memory limit that this isolate may use, in MB. Note that this is more
of a guideline instead of a strict limit. A determined attacker could use 2-3 times this limit
before their script is terminated. Against non-hostile code this limit should be pretty close. The
default is 128MB and the minimum is 8MB.inspector [boolean] - Enable v8 inspector support in this isolate. See
inspector-example.js in this repository for an example of how to use this.snapshot [ExternalCopy[ArrayBuffer]] - This is an optional snapshot created from
createSnapshot which will be used to initialize the heap of this isolate.onCatastrophicError [function] - Callback to be invoked when a very bad error occurs. If
this is invoked it means that v8 has lost all control over the isolate, and all resources in use
are totally unrecoverable. If you receive this error you should log the error, stop serving
requests, finish outstanding work, and end the process by calling process.abort().onUnhandledRejection [function] - Callback to be invoked when a promise is rejected without a
handler. By default such a rejection is thrown out of whichever call into the isolate happens to
be running, which may be one unrelated to the promise. If this callback is set the rejected value
is passed to it instead and nothing is thrown. Errors thrown by the callback are ignored.importModuleDynamically [function] - Callback which resolves import() for the code this
isolate runs. It accepts two parameters: specifier and referrer, the filename used in
ScriptOrigin by the script or module which called import(). It must return
a Module which has been instantiated and evaluated, or a promise for one, since the importing
code is given that module's namespace. Nothing is cached, so answer the same specifier with the
same module. Without this option import() rejects with "Not supported".NOTE: snapshot contains compiled machine code. That means you should not accept snapshot
payloads from a user, otherwise they may be able to run arbitrary code.
ivm.Isolate.createSnapshot(scripts, warmup_script)
scripts [array]code [string] - Source code to set up this snapshot{ ...ScriptOrigin }warmup_script [string] - Optional script to "warmup" the snapshot by triggering code
compilationYou should not use this feature. It was never all that stable to begin with and has grown increasingly unstable due to changes in v8.
Note: createSnapshot does not provide the same isolate protection like the rest of
isolated-vm. If the script passed to createSnapshot uses too much memory the process will crash,
and if it has an infinite loop it will stall the process. Furthermore newer v8 features may simply
fail when attempting to take a snapshot that uses them. It is best to snapshot code that only
defines functions, class, and simple data structures.
isolate.compileScript(code) Promise
isolate.compileScriptSync(code)
code *[strinNo open issues yet, or sync has not completed.