可利用 Object.create(null) 创建无原型沙箱基座,结合 Proxy 拦截属性访问与能力裁剪,实现继承但隔离的运行时环境:父沙箱为纯净对象,子沙箱通过原型链继承只读属性,Proxy 确保写入仅限自身,敏感API被封装或屏蔽,执行上下文绑定沙箱对象且禁用隐式全局访问。

可以利用 Object.create(null) 创建无原型的纯净基础对象,再结合属性拦截与作用域链模拟,构建具备继承级沙箱隔离的运行时环境。核心在于:每个沙箱实例拥有独立的顶层对象(如 window 或 globalThis 代理),且其原型链不指向真实全局,同时子沙箱可选择性继承父沙箱的状态,而非共享引用。
1. 构建无原型、可继承的沙箱基座
使用 Object.create(null) 创建干净的空对象作为沙箱顶层,避免隐式继承 Object.prototype 方法(如 toString、hasOwnProperty),防止原型污染和意外行为泄漏:
- 父沙箱 =
Object.create(null),手动挂载安全 API(如console.log、Date.now) - 子沙箱 =
Object.create(父沙箱),天然继承父沙箱的只读属性,但可覆盖自身属性而不影响父级 - 关键点:所有属性写入都发生在子沙箱自身,读取时按原型链向上查找,实现“继承但隔离”
2. 拦截非继承属性访问与执行上下文绑定
仅靠原型链不足以隔离可变状态(如 Math.random 返回值、Array.prototype.push 副作用)。需配合 Proxy 封装沙箱对象,控制属性访问逻辑:
- 对子沙箱对象使用
new Proxy(sandbox, { get, set, has }) -
get中:若属性在子沙箱自身存在,直接返回;否则查原型链;对敏感属性(如eval、document)一律返回undefined或抛错 -
set中:禁止向原型链写入,只允许写入子沙箱自身(receiver === target),防止污染父级
3. 模拟独立执行上下文(如 this、arguments)
脚本执行时需确保 this 指向当前沙箱对象,且不能访问外部作用域变量。可通过 Function 构造器 + with(不推荐)或更安全的 vm.runInNewContext(Node.js)替代;浏览器中常用方式是:
- 将代码包装为函数:
const fn = new Function('sandbox', 'with(sandbox){' + code + '}') - 调用时传入沙箱代理对象:
fn.call(sandboxProxy, sandboxProxy) - 注意:现代环境应禁用
with,改用显式属性访问 +Proxy的apply拦截来重写函数调用行为
4. 隔离副作用与资源访问(时间、随机数、网络等)
继承级隔离不等于安全执行。必须显式提供受控的“能力”(capabilities),而非暴露原始全局:
- 时间:挂载
sandbox.Date = { now: () => Date.now() },避免暴露完整Date构造器 - 随机数:提供封装后的
math: { random: () => Math.random() },而非整个Math - 网络/IO:完全不暴露
fetch、XMLHttpRequest,或由宿主注入带白名单校验的代理函数 - 子沙箱若需新能力,应通过父沙箱显式委托(如
parentSandbox.createNetworkClient()),而非自行获取
不复杂但容易忽略的是:原型链继承只解决属性读取,真正的隔离需要组合 Object.create、Proxy 和能力裁剪三者。子沙箱看似“继承”父级,实则无法修改父级,也无法穿透到真实全局——这才是继承级沙箱隔离的本质。










