最新发布《数智时代的AI人才粮仓模型解读白皮书(2024版)》,立即领取! 了解详情
写点什么

如何安全的运行第三方 JavaScript 代码(上)?

  • 2019-09-18
  • 本文字数:3817 字

    阅读完需:约 13 分钟

如何安全的运行第三方JavaScript代码(上)?

在本文中,我们将为读者详细介绍如何在自己的软件中安全地运行第三方 JavaScript 代码。



最近,我们团队完成了 Figma 插件 API 的开发工作,这样第三方开发人员就可以直接在基于浏览器的设计工具中运行代码。这为第三方开发人员带来便利的同时,也给我们带来许多严峻挑战,比如,如何确保插件中运行的代码不会带来安全问题?


让人更头痛的是,我们的软件是建立在非常规的堆栈之上,因此面临许多工具所没有的约束。我们的设计编辑器是建立在WebGLWebAssembly的基础之上的,其中一些用户界面是利用 Typescript&React 来实现的。并且,我们的软件支持多人同时编辑文件。在这个过程中,浏览器技术为我们提供了很大的支持,同时,也带来了许多的限制。


这篇文章将带你了解我们对完美插件解决方案的探索过程。最终,我们的问题可以归结为一点:如何安全、稳定和高效地运行插件?以下是我们面临的重要约束的简要概述:


1、安全性:插件只有在显示启动时才能访问文件。插件应该被限制在当前文件中。插件不能像 figma.com 那样进行调用。插件不能访问对方的数据,除非是自愿提供的。插件不能篡改 Figma UI 及其行为来误导用户(例如网络钓鱼)。


2、稳定性:插件不能降低 Figma 的速度,使其无法使用。插件不能破坏我们产品中的关键不变量,比如让每个人在查看同一个文件时总是看到相同内容的属性。为了查看文件,不需要管理跨设备/用户的插件安装。对 Figma 产品或内部 API 的修改不会破坏现有的插件。


3、易于开发:插件应该易于开发,以支持充满活力的生态系统。我们的大多数用户都是设计师,可能对 JavaScript 经验不多。开发人员应该能够使用现有的调试工具。


4、性能:插件应该运行得足够快,以支持大多数常见的场景,例如搜索文档、生成图表等等。


尝试 #1:沙箱方法


在我们最初几周的研究工作中,我们尝试了多种第三方代码沙箱,其中一些使用了诸如代码到代码间转换的技术。然而,大多数沙箱都没有在应用程序产品中经过长时间的历练,因此,使用这些沙箱肯定存在一定的风险。


最后,作为我们的第一次尝试,我们使用了最接近标准沙箱解决方案的一种方法:标签。该方法适用于需要运行第三方代码的应用程序,如 CodePen。


需要注意的是,这里的并不是我们平常使用的 HTML 标签。要理解方法为什么能够提供安全性,就必须先来了解一下它提供了哪些特性。


一般来说,通常用于将一个网站嵌入到另一个网站中。例如,在下图中,你可以看到Yelp.com网站中嵌入了Google.com/Maps,这样就可以为用户提供地图功能。



在这里,我们当然不希望因 Yelp 嵌入谷歌地图功能就能读取 Google 网站的内容,因为那里可能存有用户的私人信息。同样,你也不希望谷歌因此而获得了访问 Yelp 网站的内容权限。


这意味着之间的通信应该受到浏览器的严格限制。当的来源与其容器(如yelp.com与google.com)不同时,它们应该是完全隔离的。同时,与进行通信的唯一方法是通过消息传递。实际上,这些消息就是一些纯字符串。收到消息后,每个网站都可以对这些消息采取相应的行动,也可以对它们置之不理。


事实上,它们是如此独立,以至于 HTML 规范允许浏览器将列为单独进程,只要他们喜欢的话。


既然了解了的工作原理,我们就可以通过在每次插件运行时创建一个新的,并将插件的代码粘贴在中来实现插件,这样,插件可以在中做任何想做的事情。但是,除非消息通过了显示的白名单检测,否则,它无法与 Figma 文档进行交互。也是一种特殊的 null 源,这意味着向 figma.com 发送请求的尝试都会被浏览器的跨源资源共享策略所拒绝。



实际上,在这里充当了插件的沙箱角色,而浏览器供应商则为我们提供了沙箱的安全保证,毕竟他们多年来一直在忙着搜索和修复沙箱中的各种漏洞。


使用这个沙箱模型的实际插件将使用我们添加到沙箱中的一个应用程序接口,具体如下所示:


const scene = await figma.loadScene() // gets data from the main threadscene.selection[0].width *= 2scene.createNode({  type: 'RECTANGLE',  x: 10, y: 20,  ...})await figma.updateScene() // flush changes back, to the main thread
复制代码


这里的重点在于,插件是通过调用 loadScene(它向 Figma 发送消息以获取文档的副本)来进行初始化,并通过调用 updateScene(将插件所修改的发送回 Figma)作为其结束的。请注意:


  • 我们是通过获取文档的副本,而不是使用消息传递来完成属性的读取和写入操作。传递消息时,每次往返需耗时 0.1ms,这样的话,每秒只能传递 1000 条左右的消息。

  • 我们不会让插件直接使用 postMessage,因为这样做很麻烦。


决定采用这种方法后,我们大约用了一个月的时间构建好相应的 API。当时来看,马上就大功告成了,我们甚至邀请了一些 alpha 测试人员。然而,我们很快就发现,这种方法存在两大缺陷。


问题 1:async/await 关键字对用户来说不够友好


我们得到的第一反馈是,人们讨厌使用 async/await 关键字——但是在这种方法中,这是不可避免的。消息传递本质上就是异步操作,而在 JavaScript 中是没有办法对异步操作进行同步阻塞式的调用。


对于这种方法,我们不仅需要使用 await 关键字,同时还需要将所有调用函数标签为 async。综上所述,异步/等待仍然是一个比较新颖的 JavaScript 功能,要想玩转它,需要对并发性概念有相当深入的理解——很明显,这对于我们的插件开发人员来说,要求太高。


不过,如果只需要在插件开头和结尾处各使用一次 await 关键字的话,情况就没有那么糟糕。我们只需要告知开发人员始终将 await、loadScene 和 updateScene 搭配使用即可,即使他们不太了解它们的作用,影响也不大。


问题是某些 API 调用需要运行许多复杂的逻辑。例如,有时更改某图层上的单个属性后,必须同时更新其他多个图层。例如,调整 frame 的大小后,需要递归地将约束应用于其子 frame。


这些行为通常涉及许多行为复杂且差别细微的算法。如果因插件而重新实现这些算法的话,肯定不是一个好主意。此外,这些逻辑还会被编译到 WebAssembly 二进制文件中,因此,重用起来并不容易。如果我们不在插件沙箱中运行这些逻辑的话,插件将会读取过时的数据。


所以,尽管这种方法具有一定的可行性,但还是比较麻烦。例如:


await figma.loadScene()... do stuff ...await figma.updateScene()
复制代码


即使是经验丰富的工程师,事情也很快变得非常棘手:


await figma.loadScene()... do stuff ...await figma.updateScene()await figma.loadScene()... do stuff ...await figma.updateScene()await figma.loadScene()... do stuff ...await figma.updateScene()
复制代码


问题 2:场景的复制成本很贵


方法的第二个问题是,在将文档的大部分内容发送到插件之前,需要先对其进行序列化。


事实证明,人们有时会在 Figma 软件中创建非常非常大的文档,甚至达到内存的上限。例如,对于微软的设计系统文件(去年我们花了一个月时间对其进行优化)来说,将文档序列化并将其发送给插件就需要花费 14 秒时间——这些还是发生在插件运行之前。鉴于大多数插件都涉及快速的操作,例如“交换选中的两个对象”,这将使插件的可用性作废。


以增量方式加载数据或延后加载数据也不是一个好的选择,因为:


1.要想重构核心产品,至少需要花上几个月的时间。


2.所有需要等待尚未到达的数据的 API,都是异步的。


总之,因为 Figma 文档可能包含大量相互依赖的数据,所以对我们来说是不可取的。


主线程方法


由于排除了方法,我们不得不另觅他途。此后的两个周,我们又尝试了许多方法,但是或多或少存在某些不可接受的缺陷:


  • API 难以使用(例如,需要使用 REST API 或类似 GraphQL 的方法访问文档)

  • 需要借助浏览器供应商已放弃或正打算放弃的浏览器功能(例如,同步 xhr 请求+Service Worker、共享缓冲区)

  • 需要大量的研究工作或重新构建我们的应用程序,这可能需要花费几个月的时间来验证其可行与否(例如,通过 CRDT,利用 iframe+sync 方式加载 Figma 的副本等)


最终,我们终于得出结论:必须找到一种方法来创建一个模型,其中插件可以直接操作文档。这样,编写插件在一定程度上就是实现手动操作的自动化,为此,我们必须允许插件在主线程上运行。(本文转自嘶吼)


(未完待续)


原文链接:


https://www.4hou.com/technology/20153.html


2019-09-18 16:462377

评论

发布
暂无评论
发现更多内容

1000个字带你一次性搞懂JavaAgent技术,反正我是彻底服了

程序员啊叶

Java 编程 程序员 java面试 构架

BATM面试Java岗:精选200+面试题及答案、6大重点规划和经验总结

程序员啊叶

Java 编程 程序员 架构 java面试

离谱!这本书居然将高深莫测的Java高并发知识讲解得浅显易懂

了不起的程序猿

Java Java并发 java程序员

兆骑科创海内外高层次创新创业人才服务平台,双创成果转化平台

兆骑科创凤阁

怒冲GitHub榜首!京东T8幕后打造高并发面试手册,狂虐阿里面试官

程序猿阿宇

Java 高并发 阿里 构架 面试‘

阿里Java架构师面试高频300题:集合+JVM+Redis+并发+算法+框架等

程序员啊叶

优必选大型仿人服务机器人Walker X的核心技术突破

优必选科技

机器人

算法题每日一练---第9天:第几个幸运数字

知心宝贝

算法 前端 后端 7月月更

柏睿数据加入阿里云PolarDB开源数据库社区

阿里云数据库开源

开源数据库 polarDB PolarDB-X 阿里云数据库 PolarDB for PostgreSQL

CircleIndicator组件,使指示器风格更加多样化

OpenHarmony开发者

OpenHarmony

兆骑科创高质量海归人才双创服务平台,线上直播路演

兆骑科创凤阁

十字链表的存储结构

乔乔

7月月更

一次性把Docker的概念、容器与虚拟机的区别、容器交付的优势讲清

程序员啊叶

Java 编程 程序员 架构 java面试

手把手教你在 Vue3 中自定义指令

江南一点雨

闭关吃透Java性能手册,成功拿到字节Offer!不愧是阿里内部资料

程序猿阿宇

Java 后端 阿里 Java工程师 构架

HDD杭州站·HarmonyOS技术专家分享HUAWEI DevEco Studio特色功能

HarmonyOS开发者

HarmonyOS

墨天轮高分技术文档分享——数据库安全篇(共48个)

墨天轮

MySQL 数据库 oracle postgresql 数据库安全

阿里架构师花近三个月时间整理出来的Java独家面试题(Java岗)

程序员啊叶

Java 编程 程序员 架构 java面试

华为被迫开源!从认知到落地SpringBoot企业级实战手册(完整版)

程序猿阿宇

Java 程序员、 秋招 构架 面试‘

众人呼唤的 Java 单商户系统,究竟有什么过人之处?

CRMEB

终极套娃 2.0 | 云原生交付的封装

尔达Erda

云计算 程序员 微服务 云原生 开发

今天去 OPPO 面试,被问麻了

程序员啊叶

Java 编程 程序员 架构 java面试

7.依赖注入

MASA技术团队

后端

华为2023届提前批预热开始!左 神的程序代码面试指南终派上用场

程序猿阿宇

Java 算法 后端 Java工程师 算法刷题

Beyond Compare 4 实现class文件对比【最新】

白粥

工具 Beyond Compare 文件对比

哪个led显示屏厂家更好

Dylan

LED显示屏 led显示屏厂家

从业务需求出发,开启IDC高效运维之路

鲸品堂

IDC

城市燃气安全再拉警钟,如何防患于未“燃”?

AIRIOT

物联网 天然气管理平台 燃气安全

不愧是阿里内部“千亿级并发系统架构设计笔记”面面俱到,太全了

冉然学Java

Java 高并发系统设计 技术栈 构架 高并发处理

如何构建面向海量数据、高实时要求的企业级OLAP数据引擎?

字节跳动数据平台

数据仓库 云原生 OLAP Clickhouse

测试驱动开发(TDD)在线练功房 | 9月17日开课

ShineScrum捷行

敏捷 测试 TDD 代码 测试驱动开发

如何安全的运行第三方JavaScript代码(上)?_安全_Rudi Chen_InfoQ精选文章