A fully compliant 5.1 ECMAScript runner. (And mostly 6.0 ECMAScript compliant) SandBoxr runs JavaScript in a sandboxed environment.
The purpose of this library is to safely allow user generated code to be run in isolation. Code executed through the runner cannot alter state or maliciously exploit the executing environment. The primary usage is targetted towards the browser, though the server environment is supported as well. The library works by evaluating a ESTree compliant syntax tree against a virtual environment.
This library was inspired by Neil Fraser's very fine library JS Interpreter. Leveraging the Test 262 conformance suite the goals for this library became more ambitious. It became apparent that it would be feasible to completely implement the entire ECMAScript 5.1 specification. (The mocha
tests found in the "test" directory serve as a quick sanity check used during refactoring and initial development. The primary testing mechanism are the Test 262 tests.)
- Docs & Examples - would like to put together an interactive playground that can be used for demoing.
- Performance measurements/optimizations
- Add detection of infinite loops
- Allow stepping/pausing of code execution
- Possible: directly integrate external parser (with ability to override)
ES6 - ECMAScript 2015 support has been added but is not yet complete. Noteable omissions include Generators, Promises, and subclassing of built-in objects. Use the option ecmaVersion
set to 6 to enable support.
Vanilla usage without any customization, will run the code with full ES5.1 support:
// use a parser, like acorn
var ast = acorn.parse("var a = 1, b = 2; a + b;");
// pass in the parsed syntax tree into the create method
var sandbox = SandBoxr.create(ast);
// execute the code, which returns the result
var result = sandbox.execute();
// get the native value
var nativeValue = result.toNative();
nativeValue === 3;
// true
Type: Function
Default: undefined
Support for dynamic code compilation of eval
and Function(string)
are not supported unless a parsing function is indicated in the config. Example using Acorn:
var sandbox = SandBoxr.create(ast, { parser: acorn.parse });
Type: Number
Default: 5
Set this value to 6 to enable support for ES6 features.
Type: Boolean
Default: false
Forces the runner to execute all code in strict mode.
Type: Array<Object>
Default: undefined
name
: (String) The name of the code to import. This is how it will be referenced inimport
statements.ast
: (Object) The parsed AST for the code to import. ORcode
: (String) The text of the code to be imported. Requires that a parser is defined.
When the environment is initialized, any unnamed imports will be run in the order that they are defined against the global scope. Named imports are not evaluated until they are imported and are done so in their own lexical scope. Named imports can use export
statements to indicate the items to be shared.
Be sure the parser you are using is set to handle "module" source type if you are using import/export declarations.
var parser = function (text) { return acorn.parse(text, { ecmaVersion: 6, sourceType: "module" }); };
var lib = {
name: "lib",
code: "export function area (radius) { return Math.PI * Math.pow(radius, 2); }"
};
var ast = parser(`
import {area} from 'lib';
area(2);
`);
var sandbox = SandBoxr.create(ast, {
imports: [lib],
parser: parser
});
var result = sandbox.execute();
console.log(result.toNative());
// 12.566370614359172
Type: Array<String>
Default: undefined
Use the exclude
option which accepts an array of strings indicating objects or functions to exclude:
var sandbox = SandBoxr.create(ast, {
exclude: [
// remove JSON support
"JSON",
// remove console support
"console",
// remove specific methods
"String.prototype.match"
]
});
Be careful with removing primitives - don't expect to get very far without String
or Number
for example!
Type: Boolean
Default: false
Allows debugger
statements to be used. When enabled a debugger
statement is generated when the statement is hit. (Otherwise debugger
statements are ignored.)
SandBoxr.createEnvironment(): Environment
- Create an execution environment. Returns an Environment instance.
SandBoxr.create(node: AST, options: object?): SandBox
- Create a new sandbox, optionally passing in options. Returns a Sandbox instance.
SandBox.prototype.execute(env: Environment?): ObjectType|Promise
- Executes the node using the provided environment. If no environment is provided a new one is created using the provided options. The function will return the result of the execution. If async code is executed a Promise will be returned that will resolve to the execution result.
SandBox.prototype.resolve(env: Environment?): Promise
- Resolve calls the execute method but always returns a promise.
Envionment.init(options: object?): void
- Initializes the environment using the optionally provided options.
Environment.prototype.createVariable(key: string, kind: string = "var"): Reference
- Creates a new variable binding in the current scope. The kind argument is optional and indicates the kind of binding that is created. Available options are "var" (the default), "let", "const", "function", and "class". Returns a Reference.
Environment.prototype.getVariable(key: string): Reference
- Traverses up the scope chain, returning the matching variable Reference if found. (null
if the variable is not found.)
Environment.prototype.isStrict(): boolean
- Returns a boolean indicating whether the current scope is in strict mode or not.
Reference.prototype.getValue(): ObjectType
- Returns the value of the reference. The return type is ObjectType.
Reference.prototype.setValue(value: ObjectType, throwOnError: boolean = false): boolean
- Sets the value of the reference. Returns a boolean indicating whether the operation failed or succeeded. (If throwOnError
is true a failed operation will throw.)
Reference.prototype.delete(): boolean
- Deletes the reference from the current scope (or object if a property reference). Returns a boolean indicating whether the operation passed.
An instance of an ObjectFactory is attached to the environment as objectFactory
. This factory is used to create instances.
ObjectFactory.prototype.createPrimitive(value: any): ObjectType
- Creates an instance based on the primitive value passed in.
ObjectFactory.prototype.create(type: string, value: any?): ObjectType
- Creates an instance of the given type, and uses the primitive value if passed in.
ObjectFactory.prototype.createArray(elements: Array<ObjectType>?): ArrayType
- Creates an array instance and populates it with the provided elements if passed in.
ObjectFactory.prototype.createObject(ctor: FunctionType|null?): FunctionType
- Creates an object instance. If a constructor function is provided, the prototype is set using that function. If null
is passed in the object will be created with no prototype. If no object is passed in the default Object prototype is used.
To add additional objects or functions into the execution function, create the environment and add them:
var env = SandBoxr.createEnvironment();
env.init();
// add a primitive variable
var a = env.createVariable("a");
a.setValue(env.objectFactory.createPrimitive(99));
// create the object
var obj = env.objectFactory.createObject();
obj.define("doSomething", env.objectFactory.createFunction(function () {
// all arguments will be wrapped objects
// `this` is available via `this.object`
this.object == obj; // true
// todo: more documentation on available APIs
});
var o = env.createVariable("o");
o.setValue(obj);
var sandbox = SandBoxr.create(ast);
// pass the modified environment when executing the code
var result = sandbox.execute(env);
Async support is easy - just return a Promise (or any "thenable") from your method. When the promise is resolved or rejected execution will resume:
var env = SandBoxr.createEnvironment();
var fooFunc = env.objectFactory.createFunction(function () {
return $.get("/some/server/request").then(function (res) {
// remember that anything you pass back needs to be wrapped
// todo: more docs on object factory
return env.objectFactory.createPrimitive(res.value);
});
});
var foo = env.createVariable("o");
foo.setValue(fooFunc);
- "Fix" JavaScript. All those quirks you love to hate are kept intact. (There are extension points so that you can, if you so chose, alter aspects of JavaScript's implementation, for example for equality.)
- Run "safe" code. This library does not protect you from writing bad code. If you write a circular loop, expect a stack overflow.
- Transpile code. Code is not transpiled into another format - instead the syntax tree is evaluated.
- Verify syntax. This library expects a valid syntax tree. The syntax should be verified when parsed. If the syntax tree is malformed expect unexected results.
- Parse JavaScript This library must be provided an abstract syntax tree compliant with ESTree. Parser's supported include Acorn and Esprima.
- Support HTML manipulation This library does not have access to the browser environment -
document
does not exist. This is a feature.
All browsers that implement support for ECMAScript 5.1 should support this library. This includes IE9+ and other modern browsers.