⸻
Web Worker 沙箱:安全执行用户自定义代码
在可视化平台中,用户可以:
• 配置一个数据接口
• 写一段数据转换代码
• 甚至直接写一段用于渲染的 JS 逻辑
这带来两个问题:
1. 安全:恶意代码可能访问主线程资源、篡改 DOM、窃取数据
2. 稳定性:死循环、长耗时计算可能卡死主线程
⸻
- 为什么选 Web Worker 作为代码沙箱
优点:
• 独立线程,隔离主线程 DOM
• 只能通过消息通信(postMessage)交换数据
• 可随时 terminate() 中止执行(解决死循环卡死问题)
• 支持 ES 模块语法(现代浏览器)
限制:
• 不能直接访问 DOM、window、document
• 与主线程共享内存有限(只能通过 SharedArrayBuffer 或传值)
⸻
- 沙箱架构
主线程(Renderer)
├── 传入接口返回的数据
├── 传入自定义 JS 代码(字符串)
├── 传入上下文参数(hooks、utils)
↓ postMessage
Worker(Sandbox)
├── 安全运行用户代码
├── 调用提供的 API(白名单)
└── 返回转换后的数据
⸻
- Web Worker 沙箱实现
3.1 Worker 脚本(sandbox.worker.ts)
// sandbox.worker.ts
import * as Comlink from 'comlink';
interface SandboxAPI {
runUserCode(code: string, inputData: any, context?: Record<string, any>): Promise<any>;
}
const api: SandboxAPI = {
async runUserCode(code, inputData, context = {}) {
return new Promise((resolve, reject) => {
try {
const fn = new Function('data', 'context', `
"use strict";
${code.includes('return') ? `return (${code});` : code}
`);
const result = fn(inputData, context);
resolve(result);
} catch (err) {
reject(err);
}
});
}
};
Comlink.expose(api);
⸻
3.2 主线程使用 Worker
// sandboxClient.ts
import * as Comlink from 'comlink';
import SandboxWorker from './sandbox.worker?worker';
let sandboxInstance: Comlink.Remote<any> | null = null;
export async function getSandbox() {
if (!sandboxInstance) {
const worker = new SandboxWorker();
sandboxInstance = Comlink.wrap(worker);
}
return sandboxInstance;
}
// 调用
(async () => {
const sandbox = await getSandbox();
const result = await sandbox.runUserCode(
`return data.map(item => ({ ...item, name: item.name.toUpperCase() }))`,
[{ name: 'apple' }, { name: 'banana' }],
{ utils: { toUpper: (str: string) => str.toUpperCase() } }
);
console.log('转换结果', result);
})();
⸻
- 防止死循环卡死
虽然 Worker 在单独线程运行,不会卡主线程,但如果代码进入死循环,会一直占用 CPU。
解决方法:
1. 超时控制:主线程在一定时间后调用 worker.terminate() 强制结束
2. 重启 Worker:执行结束后可重置,避免内存泄露
示例:
export async function runWithTimeout(code: string, data: any, timeout = 2000) {
const worker = new SandboxWorker();
const sandbox = Comlink.wrap<any>(worker);
const timer = setTimeout(() => {
worker.terminate();
throw new Error('执行超时');
}, timeout);
const result = await sandbox.runUserCode(code, data);
clearTimeout(timer);
worker.terminate();
return result;
}
⸻
- 支持主线程传入 API(Hooks/Utils)
我们可以在调用时传递 白名单 API:
const hooks = {
formatDate: (d: string) => new Date(d).toLocaleDateString(),
sum: (arr: number[]) => arr.reduce((a, b) => a + b, 0)
};
await sandbox.runUserCode(`
return {
total: context.sum(data.numbers),
date: context.formatDate(data.date)
}
`, { numbers: [1, 2, 3], date: '2025-08-09' }, hooks);
这样用户代码只能调用 context 中暴露的 API,无法访问主线程的任意资源。
⸻
- 对比 iframe 沙箱与 Worker 沙箱
特性 iframe 沙箱 Web Worker 沙箱
用途 渲染 UMD 组件(UI 隔离) 执行用户 JS(逻辑隔离)
能访问 DOM ✅(iframe 内) ❌
线程隔离 ❌(同线程) ✅(多线程)
终止执行 ❌ ✅(terminate)
版本隔离 ✅ ❌(不处理 UI)
结论:
• iframe 沙箱:解决 UI 版本冲突、样式污染
• Web Worker 沙箱:解决逻辑执行安全、死循环控制
• 两者结合:UI 与逻辑双重隔离
⸻
- 总结
我们用 Web Worker + Comlink 实现了一个可复用的 用户自定义代码执行沙箱:
• 独立线程运行,保护主线程性能
• 支持传入数据和安全 API
• 超时控制避免死循环
• 可结合 iframe 沙箱实现 UI + 逻辑的全隔离
到这里,我们的 可视化编辑平台安全体系 已经完整成型:
1. iframe 沙箱 → 渲染 UMD 组件(隔离 UI 与版本冲突)
2. Worker 沙箱 → 执行自定义代码(隔离逻辑与死循环)
653

被折叠的 条评论
为什么被折叠?



