Several ways to implement JavaScript sandbox

preface

Sandbox, as the name suggests, is to let your program run in an isolated environment without affecting other programs outside. By creating an independent working environment similar to sandbox, the programs running inside can not have a permanent impact on the hard disk.

Usage scenario of sandbox in JS

  • Jsonp: when parsing the jsonp request returned by the server, if you do not trust the data in jsonp, you can parse and obtain the data by creating a sandbox; (when processing jsonp requests in TSW, create sandboxes to process and parse data); Execute third-party js: when it is necessary to execute third-party js and this js file is not necessarily trusted;

  • Online code editor: I believe everyone has used some online code editors, and the execution of these codes will be basically placed in the sandbox to prevent the impact on the page itself; (for example: https://codesandbox.io/s/new)

  • vue server-side rendering: in the vue server-side rendering implementation, the front-end bundle file is executed by creating a sandbox; When the createbundlerender method is called, runInNewContext can be configured in the form of true or false to judge whether a newly created sandbox object is passed in for vm to use;

  • Expression calculation in vue template: the expression calculation in vue template is placed in sandbox, and only a white list of global variables, such as Math and Date, can be accessed. You cannot attempt to access user-defined global variables in template expressions.

Implementation mode

Implementation of sandbox environment based on iframe

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

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

// Limit iframe code execution
frame.sandbox = 'allow-same-origin';

const data = [1, 2, 3, 4, 5, 6];
let newData = [];

// Send message to iframe from current page
frame.onload = function (e) {
  frame.contentWindow.postMessage(data);
};

document.body.appendChild(frame);

// Processing after 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 messages 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,
);

More about iframe sandbox:

github.com/xitu/gold-miner/blob/ma...

Related implementation libraries:

github.com/asvd/jailed

Implementation of sandbox environment based on Proxy

Another mainstream sandbox now uses with + Proxy to implement sandbox. This method is often used for js isolation. For example, the micro front-end framework realizes js isolation through this method, so there is no interference between micro applications.

with keyword

When JavaScript finds a variable that does not use a namespace, it will find it by acting on the chain. The with keyword enables the search to start from the attribute of the object. If the object has no attribute to be found, it will find it along the scope chain of the upper level. If there is no attribute to be found, it will return a ReferenceError exception.

with is not recommended. This tag has been disabled in ECMAScript 5 strict mode. The recommended alternative is to declare a temporary variable to hold the properties you need.

Advantages and disadvantages of performance

  • Advantage: the with statement can reduce the length of variables without causing performance loss. The amount of additional calculation caused by it is very small. Using 'with' can reduce unnecessary pointer path parsing operations. It should be noted that in many cases, instead of using the with statement, you can use a temporary variable to save the pointer to achieve the same effect.
  • Disadvantages: the with statement makes the program look up the variable value in the specified object first. So those variables that are not the attributes of this object will be very slow to find. If the 'with' attribute is specified in the following statement, it should only be included in the 'with' attribute of the object

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

ES6 Proxy

Proxy is a new syntax provided by ES6. The proxy object is used to create a proxy for an object, so as to intercept and customize basic operations (such as attribute search, assignment, enumeration, function call, etc.). Examples are as follows:

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

Symbol.unscopables

Symbol.unscopables are used to specify the value of an object, its object itself, and inherited attribute names that are excluded from the with environment binding of the associated object. Symbol. If unscopables is set to true, it will ignore the scope of with and go directly to the superior to find it, resulting in escape. Examples are as follows:

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

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

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

In JavaScript, there are many default settings for symbol Property of unscopables. For example:

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

Sandbox implementation

Through the above understanding of with and Proxy, we can build an interceptable object to prevent code escape in the sandbox and pollute 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, {
      // Block all properties to prevent scope chain lookup outside the Proxy object.
      has(target, key) {
        return true;
      },
      get(target, key, receiver) {
        // Reinforce to prevent escape
        if (key === Symbol.unscopables) {
          return undefined;
        }
        return Reflect.get(target, key, receiver);
      },
    });
    return fn(proxy);
  };
}

We can also use object Free to prevent the prototype chain from being modified.

Existing problems

  • code can close the with context of sandbox in advance, such as'} alert(this); {';
  • eval and new Function can be used in code to escape directly

Because no suitable solution has been found for the above problems, this method is not suitable for executing untrusted third-party code.

Sandbox principle of micro front-end frame qiankun:

juejin.cn/post/6920110573418086413

SES still under proposal

This feature is still in the proposal, but it can be used in most engines. It supports ESM module calls and can also be directly introduced through < script >.

This feature is mainly through object Free to isolate the security sandbox so that the third-party code can be executed safely. The use method is as follows:

<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>

Related documents:

www.npmjs.com/package/ses

Since this feature is still under proposal, it may be greatly changed in the future. For example, it was originally implemented by iframe, but now it has been implemented by proxy + object Free.

It also benefits from giving up the use of iframe, so that the code can be executed synchronously and there is no need to use postMessage to communicate asynchronously.

contrast

Implementation mode iframe with + Proxy SES
compatibility IE10+ IE is not supported Still in draft
Implementation mode commonly Complex, many boundary conditions need to be considered Simple, just call a simple API
Synchronous / asynchronous asynchronous synchronization synchronization
Usage scenario Most scenarios require sandbox isolation or unsafe code execution Use only scenes that need to be isolated from the sandbox Most are scenarios that require sandboxes.

summary

This time, I will introduce three methods to realize sandbox, namely iframe, with + Proxy and SES.

However, the above implementation methods are not suitable for executing untrusted third-party code. For example, the code with infinite loop in the code will cause page blocking because the above methods are in the same thread as the main thread. There is an imperfect solution to this problem Solution.

Above mentioned jailed Because the library is implemented based on Web Worker, it can avoid page jamming caused by the above dead cycle problem.

This article was first published in Personal blog

Keywords: Javascript Sandbox

Added by ReDucTor on Sat, 01 Jan 2022 13:43:22 +0200