在多年的开发生涯中,我带领团队交付了无数中等规模的外包项目,遇到过各种棘手的技术挑战。但最近在调试一个复杂的多 iframe 应用时,我发现了一个被大多数开发者忽略的 JavaScript 特性,它彻底改变了我对代码安全性的认知。
======================================================================================================================
从一次生产事故说起
想象这个场景:你为客户的xx平台开发了一个插件系统,允许商家编写自定义 JavaScript 来增强店铺功能。一切都很美好,直到某个商家写了这样的代码:
javascript
// 某个"创新"商家的插件代码
Array.prototype.push = function() {
console.log("哈哈,我重写了数组方法!");
return "搞破坏了";
};
结果如何?整个xx平台的购物车、商品列表、订单系统全线崩溃。这就是典型的全局污染问题------当不可信代码与核心业务共享同一个执行环境时,灾难随时可能发生。
Realm:JavaScript 的隔离解决方案
什么是 Realm?
Realm 是 JavaScript 的隔离执行环境,可以理解为"一套全新的 JavaScript 宇宙"。每个 Realm 都拥有自己独立的内置对象和全局作用域。
javascript
// 主环境
console.log(Array); // [Function: Array]
// 创建 iframe(自动生成新 Realm)
const iframe = document.createElement('iframe');
document.body.appendChild(iframe);
// iframe 中的 Array 是全新的构造器
const iframeArray = iframe.contentWindow.Array;
console.log(Array === iframeArray); // false!
看到这个结果时,我团队的小伙伴们都惊呆了。两个 Array 构造器功能完全相同,但却是完全独立的对象,这就是 Realm 的强大之处。
Realm 的组成要素
每个 Realm 都包含完整的运行环境:
-
全局对象:浏览器中的 window 或 Node.js 中的 global
-
内置构造器:Array、Object、Function、Error 等
-
工具函数:setTimeout、fetch、JSON 等
-
原型对象:Array.prototype、Object.prototype 等基础原型
实战:三种 Realm 实现方案
方案一:ShadowRealm(未来标准)
ShadowRealm 是 TC39 标准提案(Stage 3),专为代码隔离设计:
arduino
// 创建隔离环境
const realm = new ShadowRealm();
// 安全执行不可信代码
const result = realm.evaluate(`
// 这里无法访问主环境的任何变量
const sensitiveData = "这段数据很安全";
2 + 2
`);
console.log(result); // 4
console.log(typeof sensitiveData); // undefined(完全隔离)
当前可用 polyfill:
ini
npm install shadowrealm-api
import ShadowRealm from 'shadowrealm-api';
const realm = new ShadowRealm();
const userCodeResult = realm.evaluate(userSuppliedCode);
方案二:iframe 沙箱(生产环境首选)
对于需要立即上线的项目,iframe 是最可靠的解决方案:
ini
function createSafeSandbox() {
const frame = document.createElement('iframe');
// 关键配置:限制权限
frame.sandbox = [
'allow-scripts', // 允许执行脚本
// 'allow-same-origin' // 谨慎使用:允许同源访问
].join(' ');
frame.style.display = 'none';
document.body.appendChild(frame);
return {
evaluate: (code) => frame.contentWindow.eval(code),
destroy: () => frame.remove()
};
}
// 使用示例
const sandbox = createSafeSandbox();
try {
const result = sandbox.evaluate('2 + 2');
console.log('安全计算结果:', result);
} finally {
sandbox.destroy();
}
方案三:Web Worker(纯计算场景)
对于 CPU 密集型任务,Web Worker 提供良好的隔离性:
php
// 创建隔离的工作线程
const workerCode = `
self.onmessage = function(e) {
try {
const result = eval(e.data.code);
self.postMessage({ success: true, result });
} catch (error) {
self.postMessage({ success: false, error: error.message });
}
};
`;
const blob = new Blob([workerCode], { type: 'application/javascript' });
const worker = new Worker(URL.createObjectURL(blob));
worker.onmessage = (e) => {
if (e.data.success) {
console.log('Worker 计算结果:', e.data.result);
} else {
console.error('执行出错:', e.data.error);
}
};
// 执行用户代码
worker.postMessage({
code: 'Math.pow(2, 10)' // 用户提供的代码
});
真实案例:插件系统安全改造
我们最近为一家金融科技客户重构了他们的报表插件系统。改造前,第三方插件经常导致整个系统崩溃;改造后,即使插件代码存在问题,也只会影响自身运行。
改造前的问题代码:
javascript
// 老系统:直接执行插件代码
function runPlugin(pluginCode) {
// 危险!插件可以访问和修改全局状态
return eval(pluginCode);
}
改造后的安全方案:
javascript
class PluginSandbox {
constructor() {
this.iframe = document.createElement('iframe');
this.iframe.sandbox = 'allow-scripts';
document.body.appendChild(this.iframe);
}
runPlugin(pluginCode, api) {
// 通过 postMessage 提供有限的 API
this.iframe.contentWindow.postMessage({
type: 'EXECUTE',
code: pluginCode,
api: api
}, '*');
return new Promise((resolve) => {
const handler = (event) => {
if (event.data.type === 'RESULT') {
window.removeEventListener('message', handler);
resolve(event.data.result);
}
};
window.addEventListener('message', handler);
});
}
}
何时应该使用 Realm 技术?
根据我们的项目经验,以下场景强烈推荐使用 Realm:
-
用户代码执行:在线代码编辑器、教学平台
-
第三方插件:CMS 系统、电商平台的扩展功能
-
A/B 测试:隔离不同版本的代码逻辑
-
单元测试:确保每个测试用例环境纯净
-
微前端架构:隔离不同团队开发的子应用
安全最佳实践
在多个金融级项目中,我们总结出这些安全准则:
csharp
// 安全配置示例
const SAFE_SANDBOX_CONFIG = [
'allow-scripts', // 必需:执行脚本
// 'allow-forms', // 谨慎:表单提交
// 'allow-popups', // 谨慎:弹出窗口
// 'allow-same-origin', // 危险:同源访问
// 'allow-top-navigation' // 危险:顶级导航
];
// 永远验证输入
function validateCode(code) {
const blacklist = [
'document.cookie',
'localStorage',
'XMLHttpRequest',
'fetch',
'window.parent'
];
return !blacklist.some(unsafe => code.includes(unsafe));
}
未来展望
ShadowRealm 标准落地后,JavaScript 代码隔离将变得更加简单高效。我们团队正在密切关注相关进展,并已在几个实验性项目中开始使用 polyfill 版本。