5月31日 17:12

Garfish 沙箱隔离如何实现?快照沙箱和 Proxy 沙箱怎么选?

Garfish 沙箱隔离主要解决子应用污染全局环境的问题。微前端里,每个子应用都可能写 window、注册事件、启动定时器、插入样式或改写全局对象;如果没有隔离,A 应用卸载后的副作用会影响 B 应用。Garfish 的思路是运行时拦截全局访问,卸载时清理可追踪副作用,但它不是安全容器,不能替代鉴权、CSP 和代码审查。

快照沙箱:成本低,但更像事后恢复

快照沙箱会在子应用挂载前记录一份全局环境,运行后对比变化,卸载时恢复。它的好处是理解简单,对旧浏览器更友好,也适合一次只激活一个子应用的老系统改造。边界是它不阻止运行期间的污染;如果多个子应用同时活跃,某个应用写入的全局变量可能已经被另一个应用读到了。

Proxy 沙箱:隔离更强,适合多实例

Proxy 沙箱给子应用提供代理后的全局对象。写入 window.foo 时,数据优先落在子应用自己的记录里;读取时,再按规则从沙箱或真实 window 中取。它更适合现代浏览器和多个子应用同时存在的场景。

js
function createSandbox(rawWindow) { const fakeWindow = Object.create(null) return new Proxy(fakeWindow, { get(target, key) { return key in target ? target[key] : rawWindow[key] }, set(target, key, value) { target[key] = value return true } }) }

真实实现还要处理 this 指向、不可配置属性、白名单变量和动态脚本执行上下文。踩坑点是某些第三方库强依赖真实 window,Proxy 下会出现边界行为,需要白名单透传或改为主应用共享依赖。

严格隔离还要清理副作用

只隔离变量不够。事件监听、定时器、WebSocket、全局弹层和样式标签都可能留下副作用。子应用应在生命周期中主动释放资源,不要完全依赖框架兜底。

js
let timer export function mount() { timer = window.setInterval(refresh, 5000) window.addEventListener('resize', handleResize) } export function unmount() { clearInterval(timer) window.removeEventListener('resize', handleResize) }

还有一个容易忽略的边界是异步任务。子应用卸载后,之前发出的请求可能仍会回调并修改 DOM 或状态,因此要用 AbortController、请求序号或 mounted 标记做保护。否则用户快速切换路由时,会出现旧页面数据覆盖新页面的诡异问题。这类问题不一定被沙箱捕获,因为请求回调属于业务逻辑。框架能帮你隔离全局副作用,但无法判断哪个接口结果已经过期。

追问

快照沙箱和 Proxy 沙箱怎么选?

只运行一个子应用、还要兼容旧环境时,快照沙箱更容易落地。多个子应用同时运行,或团队希望更强隔离时,应优先考虑 Proxy 沙箱。取舍点是兼容性、隔离强度和第三方库适配成本。

沙箱能保证子应用安全吗?

不能。Garfish 沙箱主要防止应用间副作用污染,不是浏览器级安全边界。恶意代码如果能执行,仍可能发请求、读可访问数据或操作 DOM,所以敏感能力必须靠权限、CSP 和后端校验控制。

为什么卸载后还有内存泄漏?

因为泄漏常来自业务自己创建的定时器、订阅、闭包引用和未关闭连接。沙箱能记录一部分全局副作用,但无法理解每个业务异步任务的意图。排查时可以反复进入和离开子应用,观察 listener、timer、DOM 节点数量是否持续上涨。

第三方库在沙箱里异常怎么办?

先确认它是否依赖真实 window、全局单例或不可配置属性。能改配置就让它使用子应用容器,不能改时再考虑白名单透传。不要因为一个库异常就整体关闭沙箱,否则隔离边界会被打穿。

标签:Garfish