【好文翻译】JavaScript 中的 realm 是什么?

本文由体验技术团队黄琦同学翻译。

原文链接: https://weizmangal.com/2022/10/28/what-is-a-realm-in-js/

github仓库地址: https://github.com/weizman/weizman.github.io/blob/gh-pages/_posts/2020-02-02-what-is-a-realm-in-js.md

前言

作为我对浏览器 JavaScript 安全性的长期研究的一部分,在过去的一年里,我一直特别关注 realms 的安全性⭐️。

由于“基于依赖库”的开发模式的兴起,JavaScript 生态系统(尤其是浏览器里面的 JavaScript 生态系统)也就更容易受到所谓的“供应链攻击”。JavaScript 中存在一种“创建新 realm”的能力,这一能力也被成功利用来对 web 应用程序实施供应链攻击(如果你想知道为什么它能被用于实施供应链攻击,建议阅读我以前发的这篇推文)。

在“raelm 安全”这一领域,我们目前还处于任重道远的状态。我希望通过引入一款开源工具来逐步解决这个问题。这是第一款开源的 realms 安全工具——Snow-JS ❄️,作者是 LavaMoat 🌋 。这个库还在早期阶段,正在演进中,敬请期待!

为了讲清楚我做这些事情的意义,我们必须先理解“什么是 realm”。显然,我们很难用一种正确、通俗、有启发性的方式去回答这个问题。

这篇文章主要围绕浏览器中的 JavaScript 进行讨论,它也有可能通用于所有的 JavaScript 环境,这我无法保证。

realm —— JavaScript 生存的世界

你可以通俗地认为,realm 大致就是一个生态系统,JavaScript 程序就生存在这个生态系统中。就如同其他任何一个生态系统一样,它里面包含了 JavaScript 程序生存所需的各种要素。

那么,哪些东西是 JavaScript 程序所需的呢?

1) 全局执行环境

在 JavaScript 中,可以有许多不同的脚本在同一环境中运行。这些脚本可以形成作用域(scopes)。作用域是一种符合规范的执行环境,值和表达式在其中“可见”或可被访问。作用域也可以堆叠成层次结构,子作用域可以访问父作用域,反过来则不行:

<script>
    (function scope1() {
        const x = 1;
        (function scope2() {
            const y = x + 2; // 3
        }());
        const z = x + y;  // Uncaught ReferenceError: y is not defined
    }());
</script>

在上面的示例中,我们展示了如何使用 JavaScript 定义作用域。但是,假如我们编写了一个 JavaScript 程序,它只声明变量,而没有声明作用域,那会怎么样呢?

这被称为“顶级声明(top level declaration)”——任何没在已定义的作用域内声明或运行的东西,它们都位于默认的最外层作用域(outer-most scope)下,即全局执行环境(global execution environment)。

在这个最外层作用域下面声明的变量,它们会被全局执行环境下的不同脚本所共享:

<script> const x = 1; </script>
<script> const y = 2; </script>
<script> const z = x + y; // 3 </script>

realm 为 JavaScript 程序提供了属于自己的一个全局执行环境

上述的示例使用的是 const,它的作用是在“声明式环境(declarative environment)”中定义了一个新东西,它与 letclassmoduleimport 以及 function 声明是一类东西。

用其他方式定义出来的东西都是在“对象式环境(object environment)”中,包括 var, function, async function, function*, async function* (* 表示生成器函数)。

注意,当 JavaScript 代码在严格模式下执行以及作为模块代码执行时,不同的声明语句通过“对象式环境(object environment)”对全局对象(global object)的影响与上述解释是有偏差的!

“声明式环境(declarative environment)”和“对象式环境(object environment)”共同构成了前面提到的全局执行环境。

除了上述内容外,“对象式环境(object environment)”还提供了所有的“内置全局对象(built-in global objects)”,因为它的基础对象就是所谓的“全局对象(global object)”。

译者注:这个章节只是由于撰写需要,简单地提了一下“declarative environment”和“object environment”的概念,对定义的介绍并不完善。经过本人的核对,我认为他对“object environment”的解释甚至是错的。如果读者想要更好地理清这些东西,建议去找一些介绍 JavaScript 词法环境的资料看一看。

2) 全局对象 global object (和内部对象 intrinsic objects)

在拥有一个像样的可以执行 JavaScript 程序的环境之后,它们还需要能够执行高级操作,包括但不限于基于平台的操作。

全局对象提供了访问内置对象(built-ins)的功能,例如各种内部对象(intrinsics)、对象(object)、API 等(无论是否特定于平台)。由于这些东西的存在,全局对象的功能变得更丰富、更全面、更有用。

你可以在浏览器中通过 window 引用到全局对象,也可以在 NodeJS 中通过 global 引用到全局对象。此外还有一个通用的 globalThis 变量,它在这两个环境中都能用来访问全局对象。

首先,我们先看一下平台无关的部分。全局对象暴露了一些内置的内部对象( built-in intrinsic objects):

  1. 值(values)(例如 undefinedInfinity 等);
  2. 函数(functions) (例如 evalparseInt 等);
  3. 构造函数(constructors) (例如 BooleanDate 等);
  4. 其他(others) (例如 JSONMath 等)

除了上面那些以外,全局对象还暴露了不同平台特有的 API。

例如浏览器中就有 fetchalertdocument 等 API。

例如 DOM 就是一套众所周知的、浏览器特有的 API。它通过全局对象暴露出来。在这里,每一个 realm 都有它唯一独立的 DOM。

我们续接上面“全局执行环境”那一章节的末尾继续讲吧。全局对象除了导出那些内置对象(built-ins)之外,它还导出“对象式环境(object environment)”下声明的内容:

// `const` 声明的东西是处于“声明式环境(declarative environment)”下的
const constant = 1;

// 因此它们无法通过全局对象去访问到
console.log(window.constant); // undefined

// 然而,

// `var` 声明的东西是处于“对象式环境(object environment)” 下的
var variable = 2;

// 因此它们可以通过全局对象去访问到
console.log(window.variable); // 2

任何平台特定的对象和 API,它们都可以通过全局对象来访问。

译者注:这句话太拗口,本人翻译不出来,机翻的逻辑也是不通顺的。所以只能退而求其次,仅保留最关键的信息,以不堵塞阅读为目标。原句在这里,有缘人看到且有兴趣的话可以帮忙翻译一下:Any platform specific objects and APIs are accessible via the global object along with all intrinsic objects and new properties declared by code.

3) JavaScript 本身

最后要提的,就是在这个 realm 的执行环境中运行的 JavaScript 代码了,它也是可以与 realm 相关联的。

对执行环境(execution environment)、全局对象(global object)或在某个 realm 下产生的任何内容进行的更改/变换/更新,也都会被关联到某个唯一特定的 realm 上。

掌握“realm”的真实概念

恭喜你🎉看完了无聊的技术定义部分。接下来开始进入“不那么严肃”的内容,一切都会顺利!

1)“现实生活”中的 Realms

我们在前面强调过,realms 是一个 JavaScript 概念,并非浏览器独有。但我接下来仍会选择基于浏览器去讨论它。

现在我们已经定义了什么是领域,是时候“一睹真容”了。

在浏览器中,默认情况下只有一个 realm,即 top main realm。这就是浏览器加载的 web 应用程序所在的 realm。

正如我们刚刚学到的,Web 应用程序位于该 realm 内,该 realm 为其提供了全局执行环境(global execution environment)、最外层作用域(outer-most scope)和全局对象(global object),这个全局对象让我们得以访问各种内部对象(intrinsic objects)、平台特定 API 等。

然而,除了那个默认的 realm 以外,web 应用程序也可以创建出新的 realm,不同的 realm 是可以共存的。并且每个新的 realm 都将拥有一份独属于自己的全局执行环境、最外层作用域、全局对象…

每个 realm 都存在于一个 agent 内, 一个 agent 下可以有多个 realm。realm 则可以有子 realm 或同级 realm。

agent 将在另一篇文章中单独介绍。现在我们只需要知道,agent 是一个实体,它向自己下辖的 realm 提供不同的资源(例如事件循环)。

译者注:在 agent 之上还有 agent cluster 的概念。它们的层级关系是这样的:agent cluster -> agent -> realm。ECMAScript 规范文档里面有详细解释了这些概念。下面的内容会涉及到这几个名词,所以先在这里介绍一下。

在浏览器中,可以通过多种方式创建 realm。它们是否是同一个 agent 的子级,取决于 realm 的性质以及它们之间的关系。

这里有些例子:

  1. 同源的两个 iframe (无论是父子关系还是兄弟关系) 将在单个 agent 下形成两个 realm。
  2. 非同源的两个 iframe (无论是父子关系还是兄弟关系) 将会在不同的两个 agent 下各自形成一个 realm。(此外,为了保持跨源站点隔离,这两个 agent 甚至也是隶属于两个运行在不同进程中的不同 agent cluster 。)
  3. top main realm 和 service worker 也属于不同的两个 agent,但是这两个 agent 属于同一个 agent cluster (web worker 也是如此)。

这些关系还决定了 realm 之间可以进行何种程度的通信。

同源 iframe 的 realm 之间共享同一个事件循环,并且可以使用 contentWindow 属性,同步且自由地访问彼此的环境:

// https://example.com
const ifr = document.createElement('iframe');
ifr.src = 'https://example.com'; // same origin
ifr.onload = () => {
    console.log(ifr.contentWindow.document.body);
    // <body></body>
}
document.body.appendChild(ifr);

但跨源 iframe 的 realm 去使用这个 API 就会受到更多的访问限制:

// https://example.com
const ifr = document.createElement('iframe');
ifr.src = '//cross.origin.com'; // cross origin
ifr.onload = () => {
    console.log(ifr.contentWindow.document.body);
    // Uncaught DOMException: Blocked a frame with origin "https://example.com" from accessing a cross-origin frame.
}
document.body.appendChild(ifr);

跨源 realm 仍然可以相互通信,但通信受到更多限制,且只能基于 postMessage() 异步 API。这在 web worker、service worker 等场景下也成立。

值得一提的是,一旦著名的 shadow realms 提案落地,很快就会出现各种有趣的补充解决方案,它们可以解决此处描述的一些限制。这是一个值得持续关注的东西!

感受一下 realm 的独特性,有助于更好理解 realm 这一概念

例如,我们加载以下网站:

<html>
    <head></head>
    <body>
        <iframe id="some_iframe"></iframe>
    </body>
</html>

这么一来,里面就会有两个不同的 realm。一个是 top main realm,另一个是 iframe 里面新建的 realm。每个 realm 都有它的唯一身份标识,具有唯一的全局对象和全局执行环境:

window === some_iframe.contentWindow // false

每个 realm 都有属于自己的一组内部对象(intrinsic objects)和基于平台的 API:

window.fetch === some_iframe.contentWindow.fetch // false
window.Array === some_iframe.contentWindow.Array // false
<html>
    <script> 
        window.top_array = []; 
    </script>
    <iframe> 
        <script> 
            window.top.iframe_array = []; 
        </script> 
    </iframe>
    <script>
        // top_array 和 iframe_array 诞生于不同的 realm(所以他们的原型不是同一个对象)
        Object.getPrototypeOf(window.iframe_array) === Object.getPrototypeOf(window.top_array) // false
    </script>
</html>

原始数据类型(Primitives)则不一样,即使跨了 realm,它们也是全等的。

window.Infinity === some_iframe.contentWindow.Infinity // true

2)身份不连续性

身份不连续性(identity discontinuity)是随着 realm 的存在而出现的一种特征性的状态。根据这一特征,我们能更明显的感知到 realm 的独特之处。

译者注:identity discontinuity 是一个心理学和社会科学上会使用到的术语,在 Javascript 的一些底层知识的文档和讨论中也有广泛地使用这个词来描述一种特别的现象,但本人找不到合适的中文翻译,所以使用的是机翻的结果。

为了充分地演示这个概念,我们将使用 instanceof 运算符进行演示。

想象一下,我们有一个第三方服务,它的功能是创建一个蓝色按钮。他被以 iframe 的形式加载(不要问为什么),以便 Web 应用程序可以按如下方式使用其服务:

<html>
    <iframe id="blue_buttons_iframe">
        <script>
            window.top.createBlueButton = function(text) {
                const button = document.createElement('button');
                button.style.color = 'blue';
                button.value = text;
                return button;
            };
        </script>
    </iframe>
    <body>
        <script>
            const blueButton = window.createBlueButton('my blue button');
            if (!blueButton instanceof HTMLButtonElement) {
                throw new Error('blue button created does not seem to actually be a button element!');
            }
            document.body.appendChild(blueButton);
        </script>
    </body>
</html>

使用 instanceof,你可以判断运算符左侧的内容是否是其右侧内容的实例。例如,由于 button 元素是 HTMLButtonElement 接口的实例,所以 document.createElement('button') instanceof HTMLButtonElement 的结果是 true,而 document.createElement('div') instanceof HTMLButtonElement 的结果是 false——因为 div 元素继承自 HTMLDivElement 而不是 HTMLButtonElement,这是显而易见的。

然而,在我们的示例中,instanceof 检查将返回 false,并且将抛出自定义错误——即使 blueButton 继承自HTMLButtonElement

这太不可思议了,是吧?出现这种情况是有原因的,虽然它确实继承自 HTMLButtonElement,但总的来说,这种情况还不足以算作 “instance of”——因为,被测试的对象必须是“生下”它的那个 realm 的接口的实例

进行 instanceof 检查的最初目的,是为了确保蓝色按钮第三方服务确实提供了按钮元素,而不是其他任何元素。但实际上蓝色按钮的来源和 HTMLButtonElement 接口的来源不是同一个realm,因此 instaceof 检查将永远返回 false

上面所描述的这个错误,是由于代码中引入了身份不连续性(identity discontinuity),这表明了 realm 及其提供的一切事物是多么的独一无二。

解决身份不连续性(identity discontinuity)不见得是容易的。在上面的示例中,将检查的代码更改为 blueButton instanceof blue_buttons_iframe.contentWindow.HTMLButtonElement 是可以解决这个问题,但这不是一个可扩展的解决方案,也不是一个便利的解决方案。

为了使一个对象成为一个接口的实例,这个对象必须来自或创建于这个接口所在的 realm。

总结

我之所以整理出这些内容,是因为我没有找到任何有用、准确、易于理解的信息,没有一些现成的信息能够向我解答 “什么是realm” “realm的定义” 这些问题。为了更深入地了解 realm 在供应链攻击和安全中的作用,首先我需要充分了解 realm,这是对我来说至关重要的。我希望这些内容对你也有用。

你可以随时在 awesome-JavaScript-realms-security 这个仓库上了解我对这个领域的研究和开发。

我还建议你更多地了解 LavaMoat 🌋 开发的工具 Snow-JS ❄️ ,以进一步了解围绕 JavaScript realm 的安全防御工作。

关于 OpenTiny

图片

OpenTiny 是一套企业级 Web 前端开发解决方案,提供跨端、跨框架、跨版本的 TinyVue 组件库,包含基于 Angular+TypeScript 的 TinyNG 组件库,拥有灵活扩展的低代码引擎 TinyEngine,具备主题配置系统TinyTheme / 中后台模板 TinyPro/ TinyCLI 命令行等丰富的效率提升工具,可帮助开发者高效开发 Web 应用。


欢迎加入 OpenTiny 开源社区。添加微信小助手:opentiny-official 一起参与交流前端技术~更多视频内容也可关注B站、抖音、小红书、视频号

OpenTiny 也在持续招募贡献者,欢迎一起共建

OpenTiny 官网:https://opentiny.design/

OpenTiny 代码仓库:https://github.com/opentiny/

TinyVue 源码:https://github.com/opentiny/tiny-vue

TinyEngine 源码: https://github.com/opentiny/tiny-engine

欢迎进入代码仓库 Star🌟TinyEngine、TinyVue、TinyNG、TinyCLI~

如果你也想要共建,可以进入代码仓库,找到 good first issue标签,一起参与开源贡献~

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/338616.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

前端安全相关

请求后端接口必须带上sign 以上主要是解决&#xff1a;除了数据泄露外&#xff0c;一些重要功能的接口如果没有做好保护措施也会被恶意调用造成DDoS、条件竞争等攻击效果 一些营销活动类的Web页面&#xff0c;领红包、领券、投票、抽奖等活动方式很常见。此类活动对于普通用户…

LOSS损失函数值是什么意思?

环境&#xff1a; Bert-VITS2-v2.3 问题描述&#xff1a; LOSS损失函数值是什么意思&#xff1f; 解决方案&#xff1a; 在机器学习和深度学习中&#xff0c;损失函数&#xff08;Loss Function&#xff09;用来衡量模型预测值与实际值之间的差异或误差。LOSS损失函数值是…

Kaggle之旅3

Kaggle之旅3 文章目录 Kaggle之旅3前言一、Predict survival on the Titanic and get familiar with ML basics二、开始1.基础知识构造随机森林的4个步骤 2.结合教程继续 总结 前言 今天继续Kaggle之旅&#xff0c;尝试Titanic - Machine Learning from Disaster 一、Predict…

java eazyexcel 实现excel的动态多级联动下拉列表(1)使用名称管理器+INDIRECT函数

原理 将数据源放到一个新建的隐藏的sheet中将选项的子选项的对应字典设置到名称管理器中&#xff08;名称是当前选项的内容&#xff0c;值是他对应的子菜单的单元格范围&#xff0c;在1里面的sheet中&#xff09;子菜单的数据根据INDIRECT函数去左边那个单元格获取内容&#x…

如何实现 H5 秒开?

我在简历上写了精通 H5&#xff0c;结果面试官上来就问&#xff1a; 同学&#xff0c;你说你精通 H5 &#xff0c;那你能不能说一下怎么实现 H5 秒 由于没怎么做过性能优化&#xff0c;我只能凭着印象&#xff0c;断断续续地罗列了几点&#xff1a; 网络优化&#xff1a;http2、…

5G_射频测试_测试模式解读(三)

Downlink test models FR1 test model 1.1 (NR-FR1-TM1.1)&#xff08;满PRB&#xff0c;QPSK&#xff09;FR1 test model 1.2 (NR-FR1-TM1.2)( QPSK/boosted/40% QPSK)FR1 test model 2 (NR-FR1-TM2)(64QAM 只有1个PRB 功率最低)FR1 test model 2a (NR-FR1-TM2a) )(256QAM 只…

最小二乘平面拟合(高斯牛顿法)

欢迎关注更多精彩 关注我&#xff0c;学习常用算法与数据结构&#xff0c;一题多解&#xff0c;降维打击。 本期话题&#xff1a;最小二乘平面拟合 背景 ptb认证 ptb是对几何体拟合算法的认证。 主要涉及3D直线&#xff0c;3D圆&#xff0c;平面&#xff0c;球&#xff0c…

零食折扣店,注定昙花一现?

年终岁末&#xff0c;又到了各类休闲零食产品一年一度的销售旺季。与过去不同的是&#xff0c;近年来的休闲零食赛道正因大量零食折扣店的涌现而显得热闹非凡。 随着主打折扣、低价的零食折扣店成为消费者特别是三四线下沉市场消费者的新宠&#xff0c;资本开始涌入并快速推动…

com域名注册腾讯云价格

腾讯云com域名首年价格&#xff0c;企业新用户注册com域名首年1元&#xff0c;个人新用户注册com域名33元首年&#xff0c;非新用户注册com域名首年元85元一年&#xff0c;优惠价75元一年&#xff0c;com域名续费85元一年。腾讯云百科txybk.com分享腾讯云com域名注册优惠价格&a…

tag 标签

tag 标签 在使用 Git 版本控制的过程中&#xff0c;会产生大量的版本。如果我们想对某些重要版本进行记录&#xff0c;就可以给仓库历史中的某一个commit 打上标签&#xff0c;用于标识。 在本章中&#xff0c;我们将会学习如何列出已有的标签、如何创建和删除新的标签、以及…

找不到vcruntime140_1.dll无法继续执行怎么办?全面分析修复方法

当系统提示vcruntime140_1.dll文件出现错误时&#xff0c;可能会引发一系列影响计算机正常运行的问题。这个特定的动态链接库文件&#xff08;DLL&#xff09;是Microsoft Visual C Redistributable的一部分&#xff0c;对于许多基于Windows的应用程序来说至关重要。一旦vcrunt…

Java streamFile

1.Stream流 1.1体验Stream流【理解】 案例需求 按照下面的要求完成集合的创建和遍历 创建一个集合&#xff0c;存储多个字符串元素 把集合中所有以"张"开头的元素存储到一个新的集合 把"张"开头的集合中的长度为3的元素存储到一个新的集合 遍历上一步得…

SQL慢语句执行的很慢,如何分析优化呢,(如何优化的呢?)

慢查询出现的情况&#xff1a; SQL执行慢如何解决&#xff1f; 可以采用MySQL自带的分析工具Explain。 通过key和key_len检查是否命中了索引&#xff08;如果你已经添加了索引&#xff0c;还可以判断索引是否失效&#xff09;通过type字段查看SQL是否有进一步优化的空间&#…

php 文件操作

目录 1.file_xxx 2.fopen 1.file_xxx 文件读写的内容都是字符串数据格式 readfile(); //读取文件内容&#xff0c;并返回文件的长度 file_get_contents(文件路径); //读取文件。支持本地文件和远程文件url file_put_contents(文件路径, 内容); //写入数据&#xff0c;保存…

Halcon图像金字塔inspect_shape_model

Halcon图像金字塔 本文将讲述一种加速模板匹配的方法——图像金字塔。在Halcon的模板匹配过程中&#xff0c;除了基于描述符的匹配之外&#xff0c;其他几种匹配方法都用到了图像金字塔。图像金字塔是按照一定的排列顺序显示的一系列图像信息&#xff0c;包括原始图像和不同尺…

AI 的未来是开源的

想象一下&#xff0c;在未来&#xff0c;人工智能不会被锁在公司的金库里&#xff0c;而是由全球创新者社区一砖一瓦地在开放中构建的。协作&#xff0c;而不是竞争&#xff0c;推动进步&#xff0c;道德考虑与原始绩效同等重要。这不是科幻小说&#xff0c;而是人工智能发展核…

LeetCode---380周赛

题目列表 3005. 最大频率元素计数 3006. 找出数组中的美丽下标 I 3007. 价值和小于等于 K 的最大数字 3008. 找出数组中的美丽下标 II 一、最大频率元素计数 这题就是个简单的计数题&#xff0c;正常遍历统计数据即可&#xff0c;关键是你要会写代码逻辑。 代码如下&…

代码随想录二刷 | 二叉树 | 把二叉搜索树转换为累加树

代码随想录二刷 &#xff5c; 二叉树 &#xff5c; 把二叉搜索树转换为累加树 题目描述解题思路递归法迭代法 代码实现递归法迭代法 题目描述 538.把二叉搜索树转换为累加树 给出二叉 搜索 树的根节点&#xff0c;该树的节点值各不相同&#xff0c;请你将其转换为累加树&…

【C++干货铺】C++11新特性——右值引用、移动构造、完美转发

个人主页点击直达&#xff1a;小白不是程序媛 C系列专栏&#xff1a;C干货铺 代码仓库&#xff1a;Gitee 目录 左值与左值引用 右值与右值引用 左值引用和右值引用的比较 左值引用总结&#xff1a; 右值引用总结&#xff1a; 左值引用的作用和意义 右值引用的使用场景和…

C# Socket通信从入门到精通(17)——单个异步UDP服务器监听一个客户端C#代码实现

前言: 我们在开发UDP通信程序时,除了开发UDP同步客户端程序,有时候我们也需要开发异步UDP服务器程序,所谓的异步最常见的应用就是服务器接收客户端数据以后,程序不会卡在数据接收这里,而是可以继续往下执行,这在实际项目中是经常会遇到的,所以说掌握异步UDP服务器程序…