JavaScript沙箱核心是可控执行而非禁止执行,需通过vm.Script+白名单上下文(Node.js)或iframe+srcdoc(浏览器)实现引擎级隔离,禁用原型链穿透和全局污染。
JavaScript 沙箱的核心目标不是“完全禁止执行”,而是“可控执行”
直接用 eval() 或 Function 构造函数运行第三方代码,等于把当前全局对象(window 或 globalThis)完全暴露出去——能读 localStorage、能发 fetch、能改 DOM、甚至能调 process.exit()(Node.js)。沙箱要解决的,是让一段代码只能访问你明确允许的 API,其余一律拦截或返回空/错误。
最轻量但有效的隔离:vm.Script(Node.js) + 严格上下文
Node.js 内置的 vm 模块提供基础能力,但它默认不自动隔离 I/O 和系统 API。关键在手动构造一个“裸”上下文:
const vm = require('vm');
const sandbox = {
console: { log: (...args) => console.log('[sandbox]', ...args) },
Math,
JSON,
// 注意:不传入 require、process、fetch、setTimeout 等
};
const script = new vm.Script('console.log("hello", Math.random());');
script.runInNewContext(sandbox); // ✅ 安全
// script.runInNewContext(globalThis); // ❌ 危险
-
runInNewContext是必须的,runInThisContext仍会污染当前作用域 -
sandbox对象里只放白名单属性;漏掉Array.prototype.push不要紧,但多加一个require就彻底失效 - 无法阻止原型链访问(如
{}.__proto__.constructor.constructor调eval),需配合vm.createContext+pro进一步加固
xy
浏览器端没有内置沙箱?那就用 iframe + srcdoc 隔离
现代浏览器中,iframe 是唯一被引擎级支持的 JS 隔离机制。它天然拥有独立全局对象、独立事件循环、独立存储空间:
const iframe = document.createElement('iframe');
iframe.sandbox = 'allow-scripts'; // 关键:禁用插件、表单提交、弹窗等
iframe.srcdoc = `
`;
document.body.appendChild(iframe);
-
iframe.sandbox属性必须显式设置,空字符串('')表示完全禁用所有权限,'allow-scripts'是最小可用组合 -
srcdoc比src="javascript:..."更安全,后者在某些浏览器中仍可能绕过 sandbox - 跨域 iframe 无法通过
contentWindow直接通信,必须用postMessage,这反而是安全优势
为什么不用 eval + with?因为它们根本不可靠
曾有人尝试这样写:
with (safeEnv) {
eval(untrustedCode);
}
但这是严重误区:
-
with不改变作用域链的底层行为,this、arguments、原型链访问依然穿透 - V8 和 SpiderMonkey 已对
with做性能降级,部分场景直接拒绝执行 - ES2015+ 环境中,
with在严格模式下语法报错:SyntaxError: Strict mode code may not include a with statement - 更隐蔽的风险:
untrustedCode中的function f() { return this; }仍能拿到原始全局对象
真正难的从来不是“跑起来”,而是“跑完之后什么都没发生”——比如没读到 cookie、没改掉原页面状态、没触发任何副作用。做到这点,必须依赖引擎层隔离(iframe)或运行时上下文重绑定(vm),而不是语法糖伪装。

xy






