什么是javascript沙箱_如何隔离不信任的代码?

JavaScript沙箱核心是可控执行而非禁止执行,需通过vm.Script+白名单上下文(Node.js)或iframe+srcdoc(浏览器)实现引擎级隔离,禁用原型链穿透和全局污染。

JavaScript 沙箱的核心目标不是“完全禁止执行”,而是“可控执行”

直接用 eval()Function 构造函数运行第三方代码,等于把当前全局对象(windowglobalThis)完全暴露出去——能读 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.constructoreval),需配合 vm.createContext + proxy 进一步加固

浏览器端没有内置沙箱?那就用 iframe + srcdoc 隔离

现代浏览器中,iframe 是唯一被引擎级支持的 JS 隔离机制。它天然拥有独立全局对象、独立事件循环、独立存储空间:

const iframe = document.createElement('iframe');
iframe.sandbox = 'allow-scripts'; // 关键:禁用插件、表单提交、弹窗等
iframe.srcdoc = `
  
`;
document.body.appendChild(iframe);
  • iframe.sandbox 属性必须显式设置,空字符串('')表示完全禁用所有权限,'allow-scripts' 是最小可用组合
  • srcdocsrc="javascript:..." 更安全,后者在某些浏览器中仍可能绕过 sandbox
  • 跨域 iframe 无法通过 contentWindow 直接通信,必须用 postMessage,这反而是安全优势

为什么不用 eval + with?因为它们根本不可靠

曾有人尝试这样写:

with (safeEnv) {
  eval(untrustedCode);
}

但这是严重误区:

  • with 不改变作用域链的底层行为,thisarguments、原型链访问依然穿透
  • V8 和 SpiderMonkey 已对 with 做性能降级,部分场景直接拒绝执行
  • ES2015+ 环境中,with 在严格模式下语法报错:SyntaxError: Strict mode code may not include a with statement
  • 更隐蔽的风险:untrustedCode 中的 function f() { return this; } 仍能拿到原始全局对象

真正难的从来不是“跑起来”,而是“跑完之后什么都没发生”——比如没读到 cookie、没改掉原页面状态、没触发任何副作用。做到这点,必须依赖引擎层隔离(iframe)或运行时上下文重绑定(vm),而不是语法糖伪装。