专栏首页code秘密花园比 eval 和 iframe 更强的新一代 JavaScript 沙箱!

比 eval 和 iframe 更强的新一代 JavaScript 沙箱!

大家好,我是 ConardLi

今天我们来看一个进入 statge3 的新的 JavaScript 提案:ShadowRealm API

JavaScript 的运行环境

领域(realm),这个词比较抽象,其实就代表了一个 JavaScript 独立的运行环境,里面有独立的变量作用域。

比如下面的代码:

<body>
  <iframe>
  </iframe>
  <script>
    const win = frames[0].window;
    console.assert(win.globalThis !== globalThis); // true
    console.assert(win.Array !== Array); // true
  </script>
</body>

每个 iframe 都有一个独立的运行环境,document 的全局对象不同于 iframe 的全局对象,类似的,全局对象上的 Array 肯定也不同。

ShadowRealm API

ShadowRealm API 是一个新的 JavaScript 提案,它允许一个 JS 运行时创建多个高度隔离的 JS 运行环境(realm),每个 realm 具有独立的全局对象和内建对象。

ShadowRealm 具有下面的类型签名:

declare class ShadowRealm {
  constructor();
  evaluate(sourceText: string): PrimitiveValueOrCallable;
  importValue(specifier: string, bindingName: string): Promise<PrimitiveValueOrCallable>;
}

每个 ShadowRealm 实例都有自己独立的运行环境,它提供了两种方法让我们来执行运行环境中的代码:

  • .evaluate():同步执行代码字符串,类似 eval()
  • .importValue():返回一个 Promise 对象,异步执行代码字符串。

shadowRealm.evaluate()

.evaluate() 的类型签名:

evaluate(sourceText: string): PrimitiveValueOrCallable;

.evaluate() 的工作原理很像 eval()

const sr = new ShadowRealm();
console.assert(
  sr.evaluate(`'ab' + 'cd'`) === 'abcd'
);

但是与 eval() 不同的是,代码是在 .evaluate() 的独立运行环境中执行的:

globalThis.realm = 'incubator realm';

const sr = new ShadowRealm();
sr.evaluate(`globalThis.realm = 'ConardLi realm'`);
console.assert(
  sr.evaluate(`globalThis.realm`) === 'ConardLi realm'
);

如果 .evaluate() 返回一个函数,为了方便在外部调用这个函数会被包装,然后在 ShadowRealm 中运行:

globalThis.realm = 'incubator realm';

const sr = new ShadowRealm();
sr.evaluate(`globalThis.realm = 'ConardLi realm'`);

const wrappedFunc = sr.evaluate(`() => globalThis.realm`);
console.assert(wrappedFunc() === 'ConardLi realm');

每当一个值传入 ShadowRealm 时,它必须是原始类型或者可以被调用的。否则会抛出异常:

> new ShadowRealm().evaluate('[]')
TypeError: value passing between realms must be callable or primitive

shadowRealm.importValue()

.importValue() 的类型签名:

importValue(specifier: string, bindingName: string): Promise<PrimitiveValueOrCallable>;

你可以直接导入一个外部的模块,异步执行并返回一个 Promise,用法:

// main.js
const sr = new ShadowRealm();
const wrappedSum = await sr.importValue('./my-module.js', 'sum');
console.assert(wrappedSum('hi', ' ', 'folks', '!') === 'hi ConardLi!');

// my-module.js
export function sum(...values) {
  return values.reduce((prev, value) => prev + value);
}

.evaluate() 一样,传入 ShadowRealms 的值(包括参数和跨环境函数调用的结果)必须是原始的或可调用的。

ShadowRealms 可以用来做什么?

  • Web IDEWeb 绘图应用等程序中运行插件等第三方代码。
  • ShadowRealms 中创建一个编程环境,运行用户代码。
  • 服务器可以在 ShadowRealms 中运行第三方代码。
  • 在 ShadowRealms 中可以运行测试,这样外部的JS执行环境不会受到影响,并且每个套件都可以在新环境中启动(这有助于提高可复用性)。
  • 网页抓取(从网页中提取数据)和网页应用测试等可以在 ShadowRealms 中运行。

与其他方案对比

eval()和Function

ShadowRealmseval()Function 很像,但比它们俩都好一点:我们可以创建新的JS运行环境并在其中执行代码,这可以保护外部的JS运行环境不受代码执行的操作的影响。

Web Workers

Web Worker 是一个比 ShadowRealms 更强大的隔离机制。其中的代码运行在独立的进程中,通信是异步的。

但是,当我们想要做一些更轻量级的操作时,ShadowRealms 是一个很好的选择。它的算法可以同步计算,更便捷,而且全局数据管理更自由。

iframe

前面我们已经提到了,每个 iframe 都有自己的运行环境,我们可以在里面同步执行代码。

<body>
  <iframe>
  </iframe>
  <script>
    globalThis.realm = 'incubator';
    const iframeRealm = frames[0].window;
    iframeRealm.globalThis.realm = 'ConardLi';
    console.log(iframeRealm.eval('globalThis.realm')); // 'ConardLi'
  </script>
</body>

ShadowRealms 相比,还是有以下缺点:

  • 只能在浏览器中使用 iframe
  • 需要向 DOM 添加一个 iframe 以对其进行初始化;
  • 每个 iframe 环境都包含完整的 DOM,这在一些场景下限制了自定义的灵活度;
  • 默认情况下,对象是可以跨环境的,这意味着需要额外的工作来确保代码安全。

Node.js 上的 vm 模块

Node.jsvm 模块与 ShadowRealm API 类似,但具有更多功能:缓存 JavaScript 引擎、拦截 import() 等等。但它唯一的缺点就是不能跨平台,只能在 Node.js 环境下使用。

用法示例:在 ShadowRealms 中运行测试

下面我们来看个在 ShadowRealms 中运行测试的小 Demo,测试库收集通过 test() 指定的测试,并允许我们通过 runTests() 运行它们:

// test-lib.js
const testDescs = [];

export function test(description, callback) {
  testDescs.push({description, callback});
}

export function runTests() {
  const testResults = [];
  for (const testDesc of testDescs) {
    try {
      testDesc.callback();
      testResults.push(`${testDesc.description}: OK\n`);
    } catch (err) {
      testResults.push(`${testDesc.description}: ${err}\n`);
    }
  }
  return testResults.join('');
}

使用库来指定测试:

// my-test.js
import {test} from './test-lib.js';
import * as assert from './assertions.js';

test('succeeds', () => {
  assert.equal(3, 3);
});

test('fails', () => {
  assert.equal(1, 3);
});

export default true;

在下一个示例中,我们动态加载 my-test.js 模块来收集然后运行测试。

唉,目前还没有办法在不导入任何东西的情况下加载模块。

这就是为什么在前面示例的最后一行中有一个默认导出。我们使用 ShadowRealm .importvalue() 方法导入 default export

// test-runner.js
async function runTestModule(moduleSpecifier) {
  const sr = new ShadowRealm();
  await sr.importValue(moduleSpecifier, 'default');
  const runTests = await sr.importValue('./test-lib.js', 'runTests');
  const result = runTests();
  console.log(result);
}
await runTestModule('./my-test.js');

在 ShadowRealms 中运行 Web 应用

jsdom 库创建了一个封装的浏览器环境,可以用来测试 Web 应用、从 HTML 中提取数据等。它目前使用的是 Node.js vm 模块,未来可能会更新为使用 ShadowRealms(后者的好处是可以跨平台,而 vm 目前只支持 Node.js)。

参考

https://2ality.com/2022/04/shadow-realms.html

https://dev.to/smpnjn/future-javascript-shadowrealms-20mg

文章分享自微信公众号:
code秘密花园

本文参与 腾讯云自媒体分享计划 ,欢迎热爱写作的你一起参与!

作者:ConardLi
原始发表时间:2022-04-13
如有侵权,请联系 cloudcommunity@tencent.com 删除。
登录 后参与评论
0 条评论

相关文章

  • Figma: 如何在 Web 上构建一个插件系统

    原文:https://www.figma.com/blog/how-we-built-the-figma-plugin-system/

    徐小夕
  • petite-vue源码剖析-沙箱模型

    在解析v-if和v-for等指令时我们会看到通过evaluate执行指令值中的JavaScript表达式,而且能够读取当前作用域上的属性。而evaluate的实...

    ^_^肥仔John
  • 看我如何利用漏洞窃取麦当劳网站注册用户密码

    本文讲述了利用不安全的加密存储(Insecure_Cryptographic_Storage)漏洞和服务端反射型XSS漏洞,实现对麦当劳网站(McDonalds...

    FB客服
  • JavaScript中的沙箱机制探秘

    最近有需求要研究下开放给用户的自动化工具,于是就顺便整理了下沙箱的相关问题。Sandbox,中文称沙箱或者沙盘,在计算机安全中是个经常出现的名词。Sandbox...

    星回
  • 动手写 js 沙箱

    市面上现在流行两种沙箱模式,一种是使用iframe,还有一种是直接在页面上使用new Function + eval进行执行。殊途同归,主要还是防止一些Hack...

    腾讯IVWEB团队
  • 跨站脚本(XSS)备忘录-2019版

    这是一份跨站脚本(XSS)备忘录,收集了大量的XSS攻击向量,包含了各种事件处理、通讯协议、特殊属性、限制字符、编码方式、沙箱逃逸等技巧,可以帮助渗透测试人员绕...

    Bypass
  • 一文彻底搞懂前端沙箱

    在一些应用中,我们希望给用户提供插入自定义逻辑的能力,比如 Microsoft 的 Office 中的 VBA,比如一些游戏中的 lua 脚本,FireFox ...

    桃翁
  • js沙箱

    沙箱,英文是sandbox,敲程序的应该都听过,或许用过类似理念的只是自己不知道,简单说就是让你的程序运行在一个隔离的环境下,不对外界的其他程序造成影响。沙箱主...

    wade
  • 为 Node.js 应用建立一个更安全的沙箱环境

    在一些应用中,我们希望给用户提供插入自定义逻辑的能力,比如 Microsoft 的 Office 中的 VBA,比如一些游戏中的 lua 脚本,FireFox ...

    五月君
  • 说说JS中的沙箱

    沙箱,即sandbox,顾名思义,就是让你的程序跑在一个隔离的环境下,不对外界的其他程序造成影响,通过创建类似沙盒的独立作业环境,在其内部运行的程序并不能对硬盘...

    腾讯技术工程官方号
  • 前端技术探索 - 你不知道的JS 沙箱隔离

    自从 2014 年 HTML5 正式推荐标准发布以来,HTML5 增加了越来越多强大的特性和功能,而在这其中,工作线程(Web Worker)概念的推出让人眼前...

    zz_jesse
  • 你不知道的JS 沙箱隔离

    自从 2014 年 HTML5 正式推荐标准发布以来,HTML5 增加了越来越多强大的特性和功能,而在这其中,工作线程(Web Worker)概念的推出让人眼前...

    winty
  • 我所理解的微前端

    当下业内的微前端字眼出现的频率比较高,于是大致了解了一下微前端 主要内容包括:什么是微前端?微前端的好处和意义是什么?做微前端应该如何做?

    epoos
  • 从场景倒推,在字节我们要什么样的微前端体系

    微前端已经不是一个新概念了,大家或多或少都听说过接触过,这里不再去做一堆定义,只是对目前业界做法的调研总结 / 概览,这篇文章面向的是还没有在业务中使用过微前端...

    ConardLi
  • 「面试常问」靠这几个浏览器安全知识顺利拿到了大厂offer(实践篇)

    比如,这个 http://store.company.com/dir/page.html 和下面这些 URL 相比源的结果如下:

    用户4456933
  • 如何安全的运行第三方 JavaScript 代码

    最近,我们团队完成了 Figma 插件 API 的开发工作,这样第三方开发人员就可以直接在基于浏览器的设计工具中运行代码。这为第三方开发人员带来便利的同时,也给...

    Java帮帮
  • 谈谈微前端领域的js沙箱实现机制

    | 导语  在过去,浏览器沙箱(sandbox)主要应用在前端安全领域,随着应用架构复杂,微前端方案的出现,js运行环境沙箱在浏览器中的需求越来越多。特别是近...

    腾讯大讲堂
  • 初探新的 JavaScript 并行特性

    简介——我们给 JavaScript 添加了一个 API,开发者可以在 JavaScript 中使用多个 worker 和共享内存来实现真正的并行算法。

    疯狂的技术宅
  • 字节跳动是如何落地微前端的

    微前端是什么:微前端是一种类似于微服务的架构,是一种由独立交付的多个前端应用组成整体的架构风格,将前端应用分解成一些更小、更简单的能够独立开发、测试、部署的应用...

    Nealyang

扫码关注腾讯云开发者

领取腾讯云代金券