JavaScript 沙箱探索

目录
  • 1、场景
  • 2、沙箱基础功能
  • 3、iframe 实现
  • 4、web worker 实现
  • 5、quickjs 实现
  • 6、结论

1、场景

最近基于 web 在做一些类似于插件系统一样的东西,所以折腾了一下 js 沙箱,以执行第三方应用的代码。

2、沙箱基础功能

在实现之前(好吧,其实是在调研了一些方案之后),确定了沙箱基于 event bus 形式的通信实现上层的功能,基础的接口如下

export interface IEventEmitter {
  /**
   * 监听事件
   * @param channel
   * @param handle
   */
  on(channel: string, handle: (data: any) => void): void;

  /**
   * 取消监听
   * @param channel
   */
  offByChannel(channel: string): void;

  /**
   * 触发事件
   * @param channel
   * @param data
   */
  emit(channel: string, data: any): void;
}

/**
 * 一个基本 js vm 的能力
 */
export interface IJavaScriptShadowbox extends IEventEmitter {
  /**
   * 执行任意代码
   * @param code
   */
  eval(code: string): void;

  /**
   * 销毁实例
   */
  destroy(): void;
}

除了通信的能力之外,还额外要求了两个方法:

  • eval: 执行一段 js 代码
  • destroy: 销毁沙箱,供内部实现处理一些清理任务

JavaScript 沙箱示意图:

下面吾辈将分别演示使用 iframe/web worker/quickjs 执行任意 js 的方法

3、iframe 实现

老实说,谈到 web 中的沙箱,可能第一时间想到的就是 iframe 了,但它是以 html 作为入口文件,而非 js,这对于希望将 js 作为入口而不一定需要显示 iframe 的场景而言就不甚友好了。

当然可以将 js 代码包裹到 html 中然后执行

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)
`);

iframe 有以下几个问题:

  • 几乎与 eval 没有什么区别(主要是使用 Object.createObjectURL 导致同源了)– 致命的
  • 可以访问所有浏览器的 api – 我们更希望它仅能访问注入的 api,而不允许访问所有 dom api

4、web worker 实现

基本上,web worker 是一个受限的 js 运行时,以 js 为入口,和 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)
`);

但同时,它确实比 iframe 要更好一点

5、quickjs 实现

使用 quickjs 的主要灵感来源于figma 构建插件系统的一篇博客quickjs 中文文档

quickjs 是什么?它是一个 JavaScript 的运行时,虽然我们最常用的运行时是浏览器和 nodejs,但也有许多其他的运行时,可以在 GoogleChromeLabs/jsvu找到更多。而 quickjs 是其中一个轻量级、嵌入式、并且支持编译为 wasm 运行在浏览器上的一个运行时,同时它对 js 的特性支持到 es2020(包括最喜爱的 Promise 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`));

优点:

  • 事实上,在安全性方面它是无可匹敌的,因为运行在不同的 vm 上,很难出现现有微前端基于 Proxy 可能出现的安全问题。
  • 虽然没有实际测试,但 figma 的那篇博客中指出浏览器的结构化克隆在处理大型对象时存在性能问题,而 quickjs 不存在这种问题。

缺点:

  • 没有全局 api,包括常见的 console/setTimeout/setInterval 都不是 js 的特性,而是浏览器、nodejs 运行时实现的,所以必须手动实现并注入,这是一个显著的缺点。
  • 无法使用浏览器的 DevTool 调试
  • 由于底层使用 c 实现,所以需要手动管理内存的释放

6、结论

最终,我们选择了基于接口实现了 web worker 与 quickjs 的 EventEmitter,并支持随时切换的能力。

到此这篇关于JavaScript 沙箱探索的文章就介绍到这了,更多相关JavaScript 沙箱内容请搜索我们以前的文章或继续浏览下面的相关文章希望大家以后多多支持我们!

(0)

相关推荐

  • 浅谈前端JS沙箱实现的几种方式

    目录 前言 iframe实现沙箱 diff方式实现沙箱 基于代理(Proxy)实现单实例沙箱 基于代理(Proxy)实现多实例沙箱 结束语 参考 前言 在微前端领域当中,沙箱是很重要的一件事情.像微前端框架single-spa没有实现js沙箱,我们在构建大型微前端应用的时候,很容易造成一些变量的冲突,对应用的可靠性面临巨大的风险.在微前端当中,有一些全局对象在所有的应用中需要共享,如document,location,等对象.子应用开发的过程中可能是多个团队在做,很难约束他们使用全局变量.有些页

  • JS沙箱模式实例分析

    本文实例讲述了JS沙箱模式.分享给大家供大家参考,具体如下: //SandBox(['module1,module2'],function(box){}); /* * * * @function * @constructor * @param [] array 模块名数组 * @param callback function 回调函数 * 功能:新建一块可用于模块运行的环境(沙箱),自己的代码放在回调函数里,且不会对其他的个人沙箱造成影响 和js模块模式配合的天衣无缝 * * */ functi

  • JavaScript 设计模式 安全沙箱模式

    命名空间 JavaScript本身中没有提供命名空间机制,所以为了避免不同函数.对象以及变量名对全局空间的污染,通常的做法是为你的应用程序或者库创建一个唯一的全局对象,然后将所有方法与属性添加到这个对象上. 复制代码 代码如下: /* BEFORE: 5 globals */ // constructors function Parent() {} function Child() {} // a variable var some_var = 1; // some objects var mo

  • JS实现闭包中的沙箱模式示例

    本文实例讲述了JS实现闭包中的沙箱模式.分享给大家供大家参考,具体如下: <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Title</title> </head> <body> </body> <script> //闭包实现模块化:沙箱模式 -->设

  • 浅谈 JavaScript 沙箱Sandbox

    前言: 说到沙箱,我们的脑海中可能会条件反射地联想到上面这个画面并瞬间变得兴致满满,不过很可惜本文并不涉及"我的世界"(老封面党了),下文将逐步介绍"浏览器世界"的沙箱. 1.什么是沙箱 在计算机安全中, 沙箱(Sandbox)是一种用于隔离正在运行程序的安全机制 ,通常用于执行未经测试或不受信任的程序或代码,它会 为待执行的程序创建一个独立的执行环境,内部程序的执行不会影响到外部程序的运行 . 例如,下列场景就涉及了沙箱这一抽象的概念: 我们开发的页面程序运行在浏

  • 浅谈Node.js 沙箱环境

    node官方文档里提到node的vm模块可以用来做沙箱环境执行代码,对代码的上下文环境做隔离. \A common use case is to run the code in a sandboxed environment. The sandboxed code uses a different V8 Context, meaning that it has a different global object than the rest of the code. 先看一个例子 const vm

  • Node.js应用设置安全的沙箱环境

    有哪些动态执行脚本的场景? 在一些应用中,我们希望给用户提供插入自定义逻辑的能力,比如 Microsoft 的 Office 中的 VBA,比如一些游戏中的 lua 脚本,FireFox 的「油猴脚本」,能够让用户发在可控的范围和权限内发挥想象做一些好玩.有用的事情,扩展了能力,满足用户的个性化需求. 大多数都是一些客户端程序,在一些在线的系统和产品中也常常也有类似的需求,事实上,在线的应用中也有不少提供了自定义脚本的能力,比如 Google Docs 中的 Apps Script,它可以让你使

  • JavaScript 沙箱探索

    目录 1.场景 2.沙箱基础功能 3.iframe 实现 4.web worker 实现 5.quickjs 实现 6.结论 1.场景 最近基于 web 在做一些类似于插件系统一样的东西,所以折腾了一下 js 沙箱,以执行第三方应用的代码. 2.沙箱基础功能 在实现之前(好吧,其实是在调研了一些方案之后),确定了沙箱基于 event bus 形式的通信实现上层的功能,基础的接口如下 export interface IEventEmitter { /** * 监听事件 * @param chan

  • quickjs 封装 JavaScript 沙箱详情

    目录 1.场景 2.简化底层 api 2.1自动调用 dispose 2.2 提供更好的创建 vm 值的方法 3.实现 console/setTimeout/setInterval 等常见 api 3.1 实现 console 3.2 实现 setTimeout 3.3 实现 setInterval 3.4 实现事件循环 4.实现沙箱与系统之间的通信 5.实现 IJavaScriptShadowbox 6.目前 quickjs 沙箱的限制 1.场景 在前文JavaScript 沙箱探索 中声明了

  • WebWorker 封装 JavaScript 沙箱详情

    目录 1.场景 2.实现 IJavaScriptShadowbox 2.1 主线程的实现 2.2 web worker 线程的实现 3.使用 WebWorkerShadowbox/WebWorkerEventEmitter 4.限制 web worker 全局 api 5.web worker 沙箱的主要优势 1.场景 在前文  quickjs 封装 JavaScript 沙箱详情 已经基于 quickjs 实现了一个沙箱,这里再基于 web worker实现备用方案.如果你不知道 web wo

  • 分享下网站开发人员应该知道的61件事

    不出意料地,他得到了一大堆回答. 通常情况下,你需要把所有人的发言从头到尾读一遍.但是,Stack Overflow有一个很贴心的设计,它允许在问题下方开设一个wiki区,让所有人共同编辑一个最佳答案.于是,就有了下面这篇文章,一共总结出六个方面共计61条"网站开发须知". 我发现,这种概述性的问题,最适合这种集合群智.头脑风暴式的回答方式了.这也是我第一次觉得,Stack Overflow做到了Wikipedia做不到的事.(难怪它最近挤进了全美前400大网站.) 在我的印象中,关于

  • 解析从小程序开发者工具源码看原理实现

    如何查看小程序开发者工具源码 下面我们通过微信小程序开发者工具的源码来说说小程序的底层实现原理.以开发者工具版本号State v1.02.1904090的源码来窥探小程序的实现思路.如何查看微信源码,对于mac用户而言,查看微信小程序开发者工具的包内容,然后进入Contents/Resources/app.nw/js/core/index.js,注释掉如下代码就可以查看开发者工具渲染后的代码. // 打开 inspect 窗口 if (nw.App.argv.indexOf('inspect')

  • 由 JavaScript 的 with 引发的探索

    目录 1. 背景 2. with 2.1. with 的性能问题 3. LHS 和 RHS 4. 执行上下文和作用域链 4.1. VO 4.2. AO 4.3. 作用域链 一下文章来源于微信公众号前端巅峰 1. 背景 某天吃饭的时候突然想到,都说 with 会有问题,那么是什么问题,是怎样导致的呢?知其然不知其所以然,在好奇心的驱使下,从 with 出发,一路追溯到 VO.AO.那么先来复习一下 with 是干嘛的吧. 2. with js 的 with 是为对象访问提供命名空间式的访问方式,w

  • javascript prototype的深度探索不是原型继承那么简单第1/3页

    1 什么是prototype JavaScript中对象的prototype属性,可以返回对象类型原型的引用.这是一个相当拗口的解释,要理解它,先要正确理解对象类型(Type)以及原型(prototype)的概念.         前面我们说,对象的类(Class)和对象实例(Instance)之间是一种"创建"关系,因此我们把"类"看作是对象特征的模型化,而对象看作是类特征的具体化,或者说,类(Class)是对象的一个类型(Type).例如,在前面的例子中,p1和

  • javascript中负数算术右移、逻辑右移的奥秘探索

    javascript中负数的算术右移和逻辑右移都十分的让人迷惑,特别是逻辑右移>>>,你会发现即使一个很小的负数,右移之后,也会得到一个无比巨大的数,这是为什么呢? 原来在逻辑右移中符号位会随着整体一起往右移动,这样就是相当于无符号数的移动了,最后得到的就是一个正数,因为符号位不存在了.首先逻辑右移产生的一定是32位的数,然后负数的符号位为1,这意味着从第32位到符号位的位置全部由1填充,这样的数能不大吗例如-1,逻辑右移0位表现形式就是1111 1111 1111 1111 1111

随机推荐