preface

A sandbox, as the name suggests, allows your programs to run in an isolated environment, with no impact on other programs outside of it. By creating a separate environment like a sandbox, programs running inside the sandbox cannot permanently affect the hard disk.

During the development process, I learned that “users want to be able to write THEIR own JS code to run”, so I had the following exploration.

Security issues arise when executing user-submitted untrusted third party code, hence the need for a sandbox to prevent code from having a global impact.

Usage scenario of sandbox in JS

  • Jsonp: When parsing jSONP requests returned by the server, if you do not trust the data in JSONP, you can create a sandbox to parse and obtain the data. (When jSONP requests are processed in TSW, sandboxes are created to process and parse the data); Execute third party JS: when you need to execute third party JS, and the JS file is not reliable;

  • Online code editors: I believe you have used some online code editors, and the execution of these code, basically placed in a sandbox, to prevent the impact of the page itself; (for example: codesandbox.io/s/new)

  • Server rendering of VUE: In the server rendering implementation of Vue, the bundle file of the front end is executed by creating a sandbox; When the createBundleRenderer method is called, it is possible to configure runInNewContext as true or false to determine whether a newly created Sandbox object is passed in for use by the VM.

  • Evaluation of expressions in VUE templates: Evaluation of expressions in VUE templates is sandboxed and can only access a whitelist of global variables, such as Math and Date. You cannot attempt to access user-defined global variables in template expressions.

contrast

In order to save time, the first comparison summary, you can see the corresponding implementation according to the situation.

implementation iframe with + Proxy SES
compatibility IE10+ Does not support IE Proposal in progress
implementation general Complex, with many boundary cases to consider Simple, just need to call a simple API
Synchronous/asynchronous asynchronous synchronous synchronous
Usage scenarios Most scenarios require sandbox isolation or unsafe code execution Use only scenarios where sandboxes need to be isolated Most are scenarios that require a sandbox.

Related implementation

  • On the basis of iframe + Worker’s co-operation: github.com/asvd/jailed

  • ses: www.npmjs.com/package/ses

  • The sandbox principle of micro front end frame qiankun: juejin.cn/post/692011…

implementation

Sandbox environment based on IFrame

On the front end, the most common approach is to use iframe to construct a sandbox. Iframe itself is a closed sandbox environment, so if the code you want to execute is not your own code and is not a trusted data source, you can use iframe to execute.

const parent = window
const frame = document.createElement('iframe')

// Restrict code iframe code execution ability
frame.sandbox = 'allow-same-origin'

const data = [1.2.3.4.5.6]
let newData = [];

// The current page sends a message to iframe
frame.onload = function (e) {
  frame.contentWindow.postMessage(data)
}

document.body.appendChild(frame);

// Iframe receives the message
const code = ` return dataInIframe.filter((item) => item % 2 === 0) `
frame.contentWindow.addEventListener('message'.function (e) {
  const func = new frame.contentWindow.Function('dataInIframe', code);

  // Send a message to the secondary page
  parent.postMessage(func(e.data))
});

// The parent page receives the message sent by iframe
parent.addEventListener('message'.function (e) {
  console.log('parent - message from iframe:', e.data);
}, false);
Copy the code

More on the Iframe Sandbox:

Github.com/xitu/gold-m…

Related implementation libraries:

github.com/asvd/jailed

Implement sandbox environment based on Proxy

The other mainstream sandbox now uses with + Proxy to implement the sandbox. This method is often used for JS isolation, such as the micro front-end framework is to achieve JS isolation through this method, so that there is no interference between micro applications.

With the keyword

JavaScript searches for variables that do not use namespaces by applying the with keyword to the chain. The with keyword enables the search to start with the properties of the object. If the object does not have any properties to search for, the with keyword searches up the chain. ReferenceError is returned.

The use of with is not recommended, as it is prohibited in ECMAScript 5 strict mode. The recommended alternative is to declare a temporary variable to hold the attributes you need.

Performance pros and cons

  • Pros: The with statement can reduce the length of a variable without causing a performance penalty. It causes little additional computation. Using ‘with’ reduces unnecessary pointer path parsing. Note that in many cases it is possible to achieve the same effect by not using the with statement but using a temporary variable to hold the pointer.
  • Con: The with statement causes programs to look up variable values in the specified object first. So variables that aren’t properties of this object are going to be slow to find. If performance is critical, the variables in the statement below ‘with’ should contain only the attributes of the specified object

Related documents: developer.mozilla.org/zh-CN/docs/…

ES6 Proxy

Proxy is a new syntax provided in ES6. Proxy objects are used to create a Proxy for an object, enabling interception and customization of basic operations (such as property lookup, assignment, enumeration, function calls, and so on). The following is an example:

const handler = {
    get: function(obj, prop) {
        return prop in obj ? obj[prop] : 37; }};const p = new Proxy({}, handler);
p.a = 1;
p.b = undefined;

console.log(p.a, p.b);      // 1, undefined
console.log('c' in p, p.c); // false, 37
Copy the code

Symbol.unscopables

Symbol.unscopables refers to the name of the property used to specify the value of the object itself and the inherited property excluded from the with environment binding of the associated object. Symbol. Unscopables sets true, ignores the scope of with and goes directly to the parent, resulting in escape. The following is an example:

const property1 = 12
const object1 = {
  property1: 42
};

object1[Symbol.unscopables] = {
  property1: true
};

with (object1) {
  console.log(property1);
  // expected output: 12
}
Copy the code

In JavaScript, there are many default Settings for symbol. unscopables. Such as:

Array.prototype[Symbol.unscopables]
/*{ copyWithin: true, entries: true, fill: true, find: true, findIndex: true, flat: true, flatMap: true, includes: true, keys: true, values: true, }*/
Copy the code

The sandbox implementation

With this knowledge of with and Proxy, we can build an interceptable object to prevent sandbox code from escaping and contaminating the global object. The code is as follows:

function compileCode(code) {
    code = `with (sandbox) { ${code}} `
    const fn = new Function('sandbox', code);
    return (sandbox) = > {
        const proxy = new Proxy(sandbox, {
            // Intercepts all attributes to prevent lookups outside the scope chain of the Proxy object.
            has(target, key) {
                return true;
            },
            get(target, key, receiver) {
                // Secure to prevent escape
                if (key === Symbol.unscopables) {
                    return undefined;
                }
                return Reflect.get(target, key, receiver); }});returnfn(proxy); }}Copy the code

You can also use Object.freeze to prevent the prototype chain from being modified.

Existing problems

  • codeCan be closed in advancesandboxwithContext, such as'} alert(this); {';
  • codeCan be used inevalnew FunctionDirect flight

Since there is no suitable solution to the above problem, this approach is not suitable for executing untrusted third party code.

The sandbox principle of micro front end frame Qiankun:

Juejin. Cn/post / 692011…

SES is still under proposal

This feature is still in the proposal, but is already available in most engines, supports ESM module calls, and can be introduced directly through

Freeze is used to isolate the security sandbox to safely execute third-party code, using the following methods:

<script src="https://unpkg.com/ses" charset="utf-8"></script>
<script>
    const c = new Compartment();
    const code = ` (function () { const arr = [1, 2, 3, 4]; return arr.filter(x => x > 2); }) `
    const fn = c.evaluate(code);
    console.log(arr); // ReferenceError: arr is not defined
    console.log(fn()); / / [3, 4]
</script>
Copy the code

Related documents:

www.npmjs.com/package/ses

Since this feature is still being proposed, there may be major changes in the future; for example, it was originally implemented by iframe, but is now implemented by Proxy + Object.freeze.

It also benefits from abandoning iframe so that code can be executed synchronously instead of using postMessage for asynchronous communication.

conclusion

This paper introduces three methods to realize sandbox, namely iframe, with + Proxy and SES.

However, the above implementation methods are not suitable for the execution of untrusted third-party code, such as the code with infinite loops in the code, because the above methods and the main thread in the same thread, will cause page blocking, for this problem, there is a not perfect solution.

The libraries, based on a Web Worker, can avoid the kind of dead-end loops that can cause pages to freeze.

Synchronization in

  • Personal blog: www.tore.moe/post/js-san…
  • Language finch: www.yuque.com/docs/share/…