JavaScript Sandbox Exploration

JavaScript Sandbox Exploration

1. Scenario

Recently I've been working on something similar to a plugin system based on the web, so I've been playing around with the js sandbox to execute code from third-party applications.

2. Basic functions of sandbox

Before implementation (well, actually after researching some solutions), it was determined that the sandbox would implement the upper layer functions based on event bus communication. The basic interface is as follows

export interface IEventEmitter {
  /**
   * Listening event * @param channel
   * @param handle
   */
  on(channel: string, handle: (data: any) => void): void;

  /**
   * Cancel listening * @param channel
   */
  offByChannel(channel: string): void;

  /**
   * Trigger event * @param channel
   * @param data
   */
  emit(channel: string, data: any): void;
}

/**
 * A basic js vm capability */
export interface IJavaScriptShadowbox extends IEventEmitter {
  /**
   * Execute arbitrary code * @param code
   */
  eval(code: string): void;

  /**
   * Destroy the instance */
  destroy(): void;
}

In addition to the ability to communicate, two additional methods are required:

  • eval : execute a piece of js code
  • destroy : destroy the sandbox, for internal implementation to handle some cleanup tasks

JavaScript sandbox diagram:

Below we will demonstrate how to use iframe/web worker/quickjs to execute arbitrary js

3. iframe implementation

To be honest, when talking about sandboxes in the web, the first thing that comes to mind is probably iframe , but it uses html as the entry file instead of js, which is not very friendly for scenarios where you want to use js as the entry but do not necessarily need to display iframe .

Of course, you can wrap the js code in html and then execute it

function evalByIframe(code: string) {
  const html = `<!DOCTYPE html><body><script>$[code]</script></body></html>`;
  const iframe = document.createElement("iframe");
  iframe.width = "0";
  iframe.height = "0";
  iframe.style.display = "none";
  document.body.appendChild(iframe);
  const blob = new Blob([html], { type: "text/html" });
  iframe.src = URL.createObjectURL(blob);
  return iframe;
}

evalByIframe(`
document.body.innerHTML = 'hello world'
console.log('location.href: ', location.href)
console.log('localStorage: ',localStorage)
`);

But iframe has the following problems:

  • Almost the same as eval (mainly using Object.createObjectURL which leads to homology) – fatal
  • Can access all browser api – We prefer it to only have access to the injected api , not all dom api

4. Web worker implementation

Basically, a web worker is a restricted js runtime that uses js as the entry point and has a communication mechanism similar to that of iframe

function evalByWebWorker(code: string) {
  const blob = new Blob([code], { type: "application/javascript" });
  const url = URL.createObjectURL(blob);
  return new Worker(url);
}

evalByWebWorker(`
console.log('location.href: ', location.href)
// console.log('localStorage: ', localStorage)
`);

But at the same time, it is indeed better than iframe

  • Only limited browser APIs are supported, including localStorage/document APIs that cannot be accessed. For details, please refer to: [MDN] Functions and classes that can be used by Web Workers
  • All injected APIs are asynchronous operations, after all, based on postMessage/onmessage

5. Quickjs implementation

The main inspiration for using quickjs comes from a blog post on figma's plugin system, quickjs Chinese documentation

What is quickjs? It is a JavaScript runtime, and while the most common runtimes we use are browsers and nodejs , there are many others, and you can find more at GoogleChromeLabs/jsvu. quickjs is a lightweight, embedded runtime that supports compilation to wasm and runs on the browser. It also supports js features up to es2020 (including the favorite Promise and async/await ).

async function evalByQuickJS(code: string) {
  const quickJS = await getQuickJS();
  const vm = quickJS.createVm();
  const res = vm.dump(vm.unwrapResult(vm.evalCode(code)));
  vm.dispose();
  return res;
}

 
console.log(await evalByQuickJS(`1+1`));

advantage:

  • In fact, it is unmatched in terms of security, because it runs on different vm , it is difficult to have security issues that may occur in existing micro-frontends based on Proxy .
  • Although there is no actual test, the blog post by figma points out that the browser's structured cloning has performance issues when dealing with large objects, while quickjs does not have this problem.

shortcoming:

  • There is no global api , including the common console/setTimeout/setInterval which are not features of js , but are implemented by the browser and nodejs runtime, so they must be implemented and injected manually, which is a significant disadvantage.
  • Unable to use the browser's DevToo debugging
  • Since the underlying implementation is in C, the release of memory needs to be managed manually.

6. Conclusion

Finally, we chose to implement the EventEmitter of web worker and quickjs based on the interface, and support the ability to switch at any time.

This is the end of this article about JavaScript sandbox exploration. For more relevant JavaScript sandbox content, please search 123WORDPRESS.COM’s previous articles or continue to browse the following related articles. I hope everyone will support 123WORDPRESS.COM in the future!

You may also be interested in:
  • Quickjs encapsulates JavaScript sandbox details
  • A brief talk about JavaScript Sandbox
  • A brief discussion on several ways to implement front-end JS sandbox
  • A brief discussion on Node.js sandbox environment
  • Setting up a secure sandbox environment for Node.js applications
  • Example of sandbox mode in JS implementation closure
  • JS sandbox mode example analysis
  • JavaScript design pattern security sandbox mode
  • WebWorker encapsulates JavaScript sandbox details

<<:  Detailed explanation of CSS BEM writing standards

>>:  Detailed explanation of mysql trigger example

Blog    

Recommend

An article to solve the echarts map carousel highlight

Table of contents Preface toDoList just do it Pre...

Vue Element-ui table realizes tree structure table

This article shares the specific code of Element-...

Mysql command line mode access operation mysql database operation

Usage Environment In cmd mode, enter mysql --vers...

HTML+CSS+JavaScript to create a simple tic-tac-toe game

Table of contents Implementing HTML Add CSS Imple...

Building FastDFS file system in Docker (multi-image tutorial)

Table of contents About FastDFS 1. Search for ima...

Shell script nginx automation script

This script can satisfy the operations of startin...

Example of Html shielding right-click menu and left-click typing function

Disable right-click menu <body oncontextmenu=s...

Solutions to Files/Folders That Cannot Be Deleted in Linux

Preface Recently our server was attacked by hacke...

How to determine if the Linux system is installed on VMware

How to determine whether the current Linux system...

Summary of SQL deduplication methods

When using SQL to extract data, we often encounte...

Use of Linux bzip2 command

1. Command Introduction bzip2 is used to compress...

vitrualBox+ubuntu16.04 install python3.6 latest tutorial and detailed steps

Because I need to use Ubuntu+Python 3.6 version t...

Beginners learn some HTML tags (2)

Related article: Beginners learn some HTML tags (1...

An example of how to optimize a project after the Vue project is completed

Table of contents 1. Specify different packaging ...