WebWorker封装JS沙箱怎样操作,WebWorker沙箱好处有啥
Admin 2022-06-22 群英技术资讯 406 次浏览
在前文 quickjs 封装 JavaScript 沙箱详情 已经基于 quickjs
实现了一个沙箱,这里再基于 web worker 实现备用方案。如果你不知道 web worker
是什么或者从未了解过,可以查看 Web Workers API
。简而言之,它是一个浏览器实现的多线程,可以运行一段代码在另一个线程,并且提供与之通信的功能。
事实上,web worker 提供了 event emitter
的 api,即 postMessage/onmessage
,所以实现非常简单。
实现分为两部分,一部分是在主线程实现 IJavaScriptShadowbox
,另一部分则是需要在 web worker
线程实现 IEventEmitter
import { IJavaScriptShadowbox } from "./IJavaScriptShadowbox"; export class WebWorkerShadowbox implements IJavaScriptShadowbox { destroy(): void { this.worker.terminate(); } private worker!: Worker; eval(code: string): void { const blob = new Blob([code], { type: "application/javascript" }); this.worker = new Worker(URL.createObjectURL(blob), { credentials: "include", }); this.worker.addEventListener("message", (ev) => { const msg = ev.data as { channel: string; data: any }; // console.log('msg.data: ', msg) if (!this.listenerMap.has(msg.channel)) { return; } this.listenerMap.get(msg.channel)!.forEach((handle) => { handle(msg.data); }); }); } private readonly listenerMap = new Map<string, ((data: any) => void)[]>(); emit(channel: string, data: any): void { this.worker.postMessage({ channel: channel, data, }); } on(channel: string, handle: (data: any) => void): void { if (!this.listenerMap.has(channel)) { this.listenerMap.set(channel, []); } this.listenerMap.get(channel)!.push(handle); } offByChannel(channel: string): void { this.listenerMap.delete(channel); } }
import { IEventEmitter } from "./IEventEmitter"; export class WebWorkerEventEmitter implements IEventEmitter { private readonly listenerMap = new Map<string, ((data: any) => void)[]>(); emit(channel: string, data: any): void { postMessage({ channel: channel, data, }); } on(channel: string, handle: (data: any) => void): void { if (!this.listenerMap.has(channel)) { this.listenerMap.set(channel, []); } this.listenerMap.get(channel)!.push(handle); } offByChannel(channel: string): void { this.listenerMap.delete(channel); } init() { onmessage = (ev) => { const msg = ev.data as { channel: string; data: any }; if (!this.listenerMap.has(msg.channel)) { return; } this.listenerMap.get(msg.channel)!.forEach((handle) => { handle(msg.data); }); }; } destroy() { this.listenerMap.clear(); onmessage = null; } }
主线程代码
const shadowbox: IJavaScriptShadowbox = new WebWorkerShadowbox(); shadowbox.on("hello", (name: string) => { console.log(`hello ${name}`); }); // 这里的 code 指的是下面 web worker 线程的代码 shadowbox.eval(code); shadowbox.emit("open");
web worker 线程代码
const em = new WebWorkerEventEmitter(); em.on("open", () => em.emit("hello", "liuli"));
下面是代码的执行流程示意图;web worker
沙箱实现使用示例代码的执行流程:
经大佬 JackWoeker
提醒,web worker
有许多不安全的 api,所以必须限制,包含但不限于以下 api
fetch
indexedDB
performance
事实上,web worker
默认自带了 276
个全局 api,可能比我们想象中多很多。
有篇 文章 阐述了如何在 web 上通过 performance/SharedArrayBuffer api
做侧信道攻击,即便现在 SharedArrayBuffer api
现在浏览器默认已经禁用了,但天知道还有没有其他方法。所以最安全的方法是设置一个 api 白名单,然后删除掉非白名单的 api。
// whitelistWorkerGlobalScope.ts /** * 设定 web worker 运行时白名单,ban 掉所有不安全的 api */ export function whitelistWorkerGlobalScope(list: PropertyKey[]) { const whitelist = new Set(list); const all = Reflect.ownKeys(globalThis); all.forEach((k) => { if (whitelist.has(k)) { return; } if (k === "window") { console.log("window: ", k); } Reflect.deleteProperty(globalThis, k); }); } /** * 全局值的白名单 */ const whitelist: ( | keyof typeof global | keyof WindowOrWorkerGlobalScope | "console" )[] = [ "globalThis", "console", "setTimeout", "clearTimeout", "setInterval", "clearInterval", "postMessage", "onmessage", "Reflect", "Array", "Map", "Set", "Function", "Object", "Boolean", "String", "Number", "Math", "Date", "JSON", ]; whitelistWorkerGlobalScope(whitelist);
然后在执行第三方代码前先执行上面的代码
import beforeCode from "./whitelistWorkerGlobalScope.js?raw"; export class WebWorkerShadowbox implements IJavaScriptShadowbox { destroy(): void { this.worker.terminate(); } private worker!: Worker; eval(code: string): void { // 这行是关键 const blob = new Blob([beforeCode + "\n" + code], { type: "application/javascript", }); // 其他代码。。。 } }
由于我们使用 ts 编写源码,所以还必须将 ts 打包为 js bundle
,然后通过 vite
的 ?raw
作为字符串引入,下面吾辈写了一个简单的插件来完成这件事。
import { defineConfig, Plugin } from "vite"; import reactRefresh from "@vitejs/plugin-react-refresh"; import checker from "vite-plugin-checker"; import { build } from "esbuild"; import * as path from "path"; export function buildScript(scriptList: string[]): Plugin { const _scriptList = scriptList.map((src) => path.resolve(src)); async function buildScript(src: string) { await build({ entryPoints: [src], outfile: src.slice(0, src.length - 2) + "js", format: "iife", bundle: true, platform: "browser", sourcemap: "inline", allowOverwrite: true, }); console.log("构建完成: ", path.relative(path.resolve(), src)); } return { name: "vite-plugin-build-script", async configureServer(server) { server.watcher.add(_scriptList); const scriptSet = new Set(_scriptList); server.watcher.on("change", (filePath) => { // console.log('change: ', filePath) if (scriptSet.has(filePath)) { buildScript(filePath); } }); }, async buildStart() { // console.log('buildStart: ', this.meta.watchMode) if (this.meta.watchMode) { _scriptList.forEach((src) => this.addWatchFile(src)); } await Promise.all(_scriptList.map(buildScript)); }, }; } // https://vitejs.dev/config/ export default defineConfig({ plugins: [ reactRefresh(), checker({ typescript: true }), buildScript([path.resolve("src/utils/app/whitelistWorkerGlobalScope.ts")]), ], });
现在,我们可以看到 web worker
中的全局 api 只有白名单中的那些了。
可以直接使用 chrome devtool
调试
直接支持 console/setTimeout/setInterval api
直接支持消息通信的
api
免责声明:本站发布的内容(图片、视频和文字)以原创、转载和分享为主,文章观点不代表本网站立场,如果涉及侵权请联系站长邮箱:mmqy2019@163.com进行举报,并提供相关证据,查实之后,将立刻删除涉嫌侵权内容。
猜你喜欢
闭包实质上是函数作用域的副产物,下面这篇文章主要给大家介绍了关于浏览器是如何看闭包的相关资料,文中通过图文介绍的非常详细,需要的朋友可以参考下
这篇文章讲述了React的基本介绍,基本使用和React相关js库.通过这篇文章可以入门React的使用,可以快速上手使用React进行代码的编写
这篇文章主要为大家介绍了在Iconfont还是不能上传,要如何维护你的Icon,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职加薪<BR>
这篇文章给大家分享的是vuex中刷新数据消失的问题的解决方法,下文有具体的问题分析、解决思路及方法,文中示例代码介绍的非常详细,感兴趣的朋友接下来一起跟随小编看看吧。
node.js怎样实现断点续传?首先,断点续传怎样理解呢?一般,如果是一些只有几M的图片或者文件,我们将它们上传的到服务器是上是很快的,但是如果是几百M或者几G的文件,那么上传到服务器的时间就会更久,如果这期间文件有断网或者网络差要重新下载情况,那么得多人抓狂呀!而使用切片+断点续传就能够很好应对这样的情况。那么具体怎样做呢?下面跟随小编一起来详细了解看看吧。
成为群英会员,开启智能安全云计算之旅
立即注册Copyright © QY Network Company Ltd. All Rights Reserved. 2003-2020 群英 版权所有
增值电信经营许可证 : B1.B2-20140078 粤ICP备09006778号 域名注册商资质 粤 D3.1-20240008