问题简述
qiankun
是一个微前端框架,可以有效地隔离主应用及各个子应用之间的运行环境,使得 window
对象不会被污染,但它并不是一个绝对安全的沙箱环境,子应用可以通过一些 “特殊技巧” 绕过沙箱。所以,如果大家遵守一定的约束和规范,qiankun 可以很好发挥作用,否则就会出现很多问题。
手动加载 script
qiankun 会通过 import-html-entry 组件,预先获取 html 文本流,然后分别收集样式表(link 标签和 style 标签)和脚本(script 标签),然后在 proxyWindow
中执行 js 脚本,但是手动加载的脚本不会被收集到:
// app entry
let script = document.createElement('script');
script.innerText = `window.nanote = 'running';`
document.body.append(script);
// main
console.log(window.nanote) // running
也可以将上述代码中的 script.innerText
改为 script.src="example.com/index.js"
来执行更丰富的内容
其他方法
- qiankun 默认 document 不拦截,所以子应用是可以操作其他应用 DOM
修改内置对象使得其他应用 “中招”
// app Array.isArray = () => true; // main Array.isArray('') // true
此时就突出了 Nanote 使用 iframe 而不是 qiankun 的必要性
一些思考
未来版本的 Nanote 将转向 iframe 方案,但仍然面临着类似的安全问题。
iframe 内部上下文完全可以通过 window.parent
访问主应用的 window
环境,尽管 IPC_API 的私密性问题 已经得到彻底解决,但是子应用仍然可以据此获取、修改主应用的 DOM 以及其他子应用的 DOM,这似乎有点危险 ㊙
因此,要分别为每个子应用设定 proxyWindow
环境,拦截一些敏感的 DOM 操作,这些都可以借助 import-html-entry
的 api 实现:
import importHtmlEntry from 'https://cdn.jsdelivr.net/npm/import-html-entry/+esm'
let appWindow = document.getElementById('app').contentWindow
let proxyWindow = new Proxy(appWindow, {
get(target, prop, receiver) {
if (prop === 'parent') return 'can not use window.parent'
if (prop === 'top') return 'can not use window.top'
return target[prop]
}
})
importHtmlEntry('./app.html').then(data => {
appWindow.document.write(data.template)
data.execScripts(proxyWindow).then((data) => {
console.log('finish')
})
})
但是仍然无法拦截手动加载的脚本对 window
的访问,要想解决这个问题,就必须禁止 <script>
标签的创建,可以通过 Proxy 中的 apply
拦截 createElement,但多少可能会造成一些性能问题,再思考和调研一阵吧,看看有没有更好的办法。