Home

Awesome

Jailed — flexible JS sandbox

Jailed is a small JavaScript library for running untrusted code in a sandbox. The library is written in vanilla-js and has no dependencies.

With Jailed you can:

The untrusted code may then interract with the main application by directly calling those functions, but the application owner decides which functions to export, and therefore what will be allowed for the untrusted code to perform.

The code is executed as a plugin, a special instance running as a restricted subprocess (in Node.js), or in a web-worker inside a sandboxed frame (in case of web-browser environment). The iframe is created locally, so that you don't need to host it on a separate (sub)domain.

You can use Jailed to:

For instance:

var path = 'http://path.to/the/plugin.js';

// exported methods, will be available to the plugin
var api = {
    alert: alert
};

var plugin = new jailed.Plugin(path, api);

plugin.js:

// runs in a sandboxed worker, cannot access the main application,
// with except for the explicitly exported alert() method

// exported methods are stored in the application.remote object
application.remote.alert('Hello from the plugin!');

(exporting the alert() method is not that good idea actually)

Under the hood, an application may only communicate to a plugin (sandboxed worker / jailed subprocess) through a messaging mechanism, which is reused by Jailed in order to simulate the exporting of particular functions. Each exported function is duplicated on the opposite site with a special wrapper method with the same name. Upon the wrapper method is called, arguments are serialized, and the corresponding message is sent, which leads to the actual function invocation on the other site. If the executed function then issues a callback, the responce message will be sent back and handled by the opposite site, which will, in turn, execute the actual callback previously stored upon the initial wrapper method invocation. A callback is in fact a short-term exported function and behaves in the same way, particularly it may invoke a newer callback in reply.

Installation

For the web-browser environment — download and unpack the distribution, or install it using Bower:

$ bower install jailed

Load the jailed.js in a preferrable way. That is an UMD module, thus for instance it may simply be loaded as a plain JavaScript file using the <script> tag:

<script src="jailed/jailed.js"></script>

For Node.js — install Jailed with npm:

$ npm install jailed

and then in your code:

var jailed = require('jailed');

Optionally you may load the script from the distribution:

var jailed = require('path/to/jailed.js');

After the module is loaded, the two plugin constructors are available: jailed.Plugin and jailed.DynamicPlugin.

Usage

The messaging mechanism reused beyond the remote method invocation introduces some natural limitations for the exported functions and their usage (nevertheless the most common use-cases are still straightforward):

In Node.js the send() method of a child process object is used for transfering messages, which serializes an object into a JSON-string. In a web-browser environment, the messages are transfered via postMessage() method, which implements the structured clone algorithm for the serialization. That algorithm is more capable than JSON (for instance, in a web-browser you may send a RegExp object, which is not possible in Node.js). More details about structured clone algorithm and its comparsion to JSON.

A plugin object may be created either from a string containing a source code to be executed, or with a path to the script. To load a plugin code from a file, create the plugin using jailed.Plugin constructor and provide the path:

var path = 'http://path.to/some/plugin.js';

// set of methods to be exported into the plugin
var api = {
    alert: alert
}

var plugin = new jailed.Plugin(path, api);

plugin.js:

application.remote.alert('Hello from the plugin!');

Creating a plugin from a string containing a code is very similar, this is performed using jailed.DynamicPlugin constructor:

var code = "application.remote.alert('Hello from the plugin!');";

var api = {
    alert: alert
}

var plugin = new jailed.DynamicPlugin(code, api);

The second api argument provided to the jailed.Plugin and jailed.DynamicPlugin constructors is an interface object with a set of functions to be exported into the plugin. It is also possible to export functions in the opposite direction — from a plugin to the main application. It may be used for instance if a plugin provides a method to perform a calculation. In this case the second argument of a plugin constructor may be omitted. To export some plugin functions, use application.setInterface() method in the plugin code:

// create a plugin
var path = "http://path.to/some/plugin.js";
var plugin = new jailed.Plugin(path);

// called after the plugin is loaded
var start = function() {
    // exported method is available at this point
    plugin.remote.square(2, reportResult);
}

var reportResult = function(result) {
    window.alert("Result is: " + result);
}

// execute start() upon the plugin is loaded
plugin.whenConnected(start);

plugin.js:

// provides the method to square a number
var api = {
    square: function(num, cb) {
        // result reported to the callback
        cb(num*num);
    }
}

// exports the api to the application environment
application.setInterface(api);

In this example the whenConnected() plugin method is used at the application site: that method subscribes the given function to the plugin connection event, after which the functions exported by the plugin become accessible at the remote property of a plugin.

The whenConnected() method may be used as many times as needed and thus subscribe several handlers for a single connection event. For additional convenience, it is also possible to set a connection handler even after the plugin has already been connected — in this case the handler is issued immediately (yet asynchronously).

When a plugin code is executed, a set of functions exported by the application is already prepared. But if one of those functions is invoked, it will actually be called on the application site. If in this case the code of that function will try to use a function exported by the plugin, it may not be prepared yet. To solve this, the similar application.whenConnected() method is available on the plugin site. The method works same as the one of the plugin object: the subscribed handler function will be executed after the connection is initialized, and a set of functions exported by each site is available on the opposite site.

Therefore:

To disconnect a plugin, use the disconnect() method: it kills a worker / subprocess immediately without any chance for its code to react.

A plugin may also disconnect itself by calling the application.disconnect() method.

In addition to whenConnected() method, the plugin object also provides similar whenFailed() and whenDisconnected() methods:

Just like as for whenConnected() method, those two methods may also be used several times or even after the event has actually been fired.

Compatibility

Jailed was tested and should work in Node.js, and in the following browsers:

Security

This is how the sandbox is built:

In a web-browser:

Note: when Jailed library is loaded from the local source (its path starts with file://), the "allow-same-origin" permission is added to the sandbox attribute of the iframe. Local installations are mostly used for testing, and without that permission it would not be possible to load the plugin code from a local file. This means that the plugin code has an access to the local filesystem, and to some origin-shared things like IndexedDB (though the main application page is still not accessible from the worker). Therefore if you need to safely execute untrusted code on a local system, reuse the Jailed library in Node.js.

In Node.js:

Warning: according to recent reports (#33) this way of sandboxing is not secure any longer, the fix is being prepared...

--

follow me on twitter: https://twitter.com/asvd0