ProxySandbox
一、认识
代理沙箱 ProxySandbox
主要是通过为每个沙盒创建一个 fakeWindow
,然后为这个fakeWindow
设置代理,通过代理来访问这个fakeWindow
。
二、特点
代理了一个全新的对象,这个对象是复制的 window
对象的一部分不可配置属性,所有的更改都是基于这个 fakeWindow
对象,从而保证多个实例之间属性互不影响
三、实现
3.1 思路
变量如下:
-
proxy
-
isRunning
-
fakeWindow
: 基于window
对象创建一个新对象,复制window
对象中不可配置的属性,(例如window
、self
、top
等),这些不可配置的属性在沙箱环境中经过特别处理,将configurable
设置为true
,如果没有get
方法,将writable
设置为true
。需要复制不可配置属性的原因:ProxySandbox
中的Proxy
对象利用fakeWindow
来实现更高层次的隔离和控制。沙箱的get
、set
、has
和deleteProperty
操作都基于fakeWindow
和全局window
对象之间的代理关系, 从而提供了对沙箱环境的进一步保护和控制。由于fakeWindow
只包含window
中不可配置的属性,而不会影响全局window
对象中的其他属性,这有助于确保沙箱环境的行为更接近真实的浏览器环境, 而且避免了在沙箱中执行的代码意外地影响全局环境。
逻辑如下:
-
createFakeWindow(globalContext, speedy)
: 基于window
对象创建一个新对象,复制window
对象中不可配置的属性,(例如window
、self
、top
等),这些不可配置的属性在沙箱环境中经过特别处理,将configurable
设置为true
,如果没有get
方法,将writable
设置为true
。 -
constructor
: 初始化沙箱实例。创建沙箱环境,设置代理对象。通过proxy
劫持了fakeWindow
的set
、get
、has
、deleteProperty
方法。 -
active()
: 激活沙箱。使沙箱进入运行状态。 -
inactive()
: 停用沙箱。使沙箱停止运行,并恢复全局变量的原始状态。 -
set
: 拦截属性设置操作。 -
get
: 拦截属性获取操作。 在沙箱中正确返回属性值,处理特殊属性。 -
has
: 拦截in
操作符。 在沙箱中正确判断属性是否存在。 -
deleteProperty
: 拦截删除属性操作。在沙箱中正确删除属性。
3.2 实现
简单实现
function createFakeWindow(globalContext) {
const fakeWindow = {};
Object.getOwnPropertyNames(globalContext)
.filter((p) => {
const descriptor = Object.getOwnPropertyDescriptor(globalContext, p);
return !descriptor?.configurable;
})
.forEach((p) => {
const descriptor = Object.getOwnPropertyDescriptor(globalContext, p);
if (descriptor) {
const hasGetter = Object.prototype.hasOwnProperty.call(
descriptor,
"get"
);
if (p === "top" || p === "parent" || p === "self" || p === "window") {
descriptor.configurable = true;
if (!hasGetter) {
descriptor.writable = true;
}
}
Object.defineProperty(fakeWindow, p, Object.freeze(descriptor));
}
});
return fakeWindow;
}
class ProxySandbox {
constructor() {
this.fakeWindow = createFakeWindow(window);
this.isRunning = false;
this.proxy = new Proxy(this.fakeWindow, {
get: (target, key) => {
if (key === "window" || key === "self" || key === "globalThis") {
return this.proxy;
}
return target[key] || window[key];
},
set: (target, key, value) => {
if (this.isRunning) {
target[key] = value;
return true;
}
return false;
},
has: (target, key) => {
return key in target || key in window;
},
deleteProperty: (target, key) => {
if (this.isRunning) {
delete target[key];
return true;
}
return false;
},
});
}
active() {
this.isRunning = true;
}
inactive() {
this.isRunning = false;
}
}
3.3 测试
const sandBox1 = new ProxySandbox("sandBox1");
const sandBox2 = new ProxySandbox("sandBox2");
sandBox1.active();
sandBox1.proxy.a = "哈哈";
console.log("sandBox1.proxy.a", sandBox1.proxy.a);
console.log("sandBox2.proxy.a", sandBox2.proxy.a);
sandBox2.active();
sandBox2.proxy.a = "哈哈修改";
console.log("sandBox1.proxy.a", sandBox1.proxy.a);
console.log("sandBox2.proxy.a", sandBox2.proxy.a);