真正安全的沙箱必须切断所有通向原始上下文的路径:用 Proxy 拦截空壳全局对象,屏蔽敏感字段,禁用 with 和动态函数逃逸,完整代理 document/location,隔离 iframe.contentWindow。

直接用 eval 或 Function 构造函数执行字符串代码,看似简单,实则危险——它默认共享宿主全局作用域,能读取 window.location、返回真实 window,甚至通过修改 Array.prototype 污染整个页面。真正安全的沙箱,必须切断所有通向原始上下文的路径。
为什么原生 eval 无法隔离执行环境
eval 的执行上下文与其调用位置完全一致:它能访问外层函数的闭包变量、当前作用域链上的所有标识符,以及顶层的 window 和 this。例如:
-
let secret = "token123"; eval("console.log(secret)")会成功输出 -
eval("this === window")返回true,暴露宿主环境 -
eval("Array.prototype.push = () => {}")会破坏所有后续数组操作
核心隔离手段:Proxy + 空壳全局对象
不传入真实 window,而是构造一个干净的空壳对象,并用 Proxy 拦截所有属性访问:
- 对
JSON、Math等安全内置对象,直接挂载只读副本 - 对
setTimeout、fetch等高危 API,重写为沙箱内受控调度器 - 拦截
get操作,显式屏蔽constructor、__proto__、document、location等敏感字段,返回undefined - 所有对
this或globalThis的访问,一律 trap 到沙箱对象本身
堵住常见逃逸通道
仅靠 Proxy 不够,恶意代码可能绕过拦截:
- 禁用
with语句:在执行前用 AST 解析(如 Acorn)检测并拒绝含with的代码 - 约束动态函数:所有
new Function(...)必须显式绑定沙箱对象,如new Function('return this').bind(sandbox)() - 重写 Promise 构造器:确保
then回调中的this始终指向沙箱,而非宿主全局 - 递归代理 DOM 节点:若模拟
document.querySelector,其返回节点的parentNode等属性也需走 Proxy
document 和 location 的模拟不能“半吊子”
很多沙箱只 mock document.createElement,却放行 document.cookie getter/setter 或让 location.href 可读可写——这等于留了后门:
-
document必须是完整代理对象,所有属性(body、cookie、domain)都经get/settrap 控制 -
location仅暴露只读字段(href、origin),值由沙箱初始化时固定,禁止运行时更新 - 对
iframe.contentWindow类接口,必须返回另一个独立沙箱实例,绝不可返回原生window

















