WebView 留下的那扇门
WebView 不是沙箱——它是带着 App 权限被塞进你应用里的一个完整浏览器引擎。Android 官方文档直白地称 WebView 本质上是"一个功能齐全的浏览器引擎(Chromium)",有"庞大的内存占用和复杂的多进程架构"。因为页面是以宿主 App 的身份运行的,宿主授予的任何能力——文件访问、已存凭据、原生桥——页面加载的任意 JavaScript 都够得着。让 Web 调用原生的那座桥,从构造上看就是一扇敞开的门:页面既是不可信的内容,又是拥有特权的表面。
这正是 WebView 沙箱问题的核心:在 Web 文档和原生 App 之间,没有一堵硬墙。一个恶意或只是有 bug 的页面可以跨桥去读共享存储、滥用宿主的权限。这也正是浏览器把每个站点隔离进独立渲染进程、iOS 给每个 App 分配一个由内核级 MAC 沙箱强制的容器——平台深知不可信代码必须被围起来。
隔离成为一等属性
小程序运行时翻转了这个默认值。它不为内嵌文档授予原生权力,而是把每个小程序放进自己的执行沙箱,并让平台经手每一次特权调用。微信的模型——整个生态复制的那套——把一个程式拆成逻辑层和视图层,两者都运行在客户端控制之下,没有直接的 DOM,也没有直接的原生句柄。平台工程剖析写道,逻辑层"没有 DOM 访问能力"、"无法读取或修改页面树",而视图层"不执行开发者编写的业务逻辑"。两者只通过一条序列化消息桥相遇。
从安全角度看,这道边界就是全部意义所在:程式的 JavaScript 碰不到别的程式的数据,伸不进宿主,也无法在没有平台显式、声明式调解的情况下调用敏感 API。隔离不是开发者记得去开的开关——它是运行时本身的形状。
墙真正挡住了什么
具体来说,运行时强制的沙箱挡住了 WebView 挡不住的三件事:
| 风险 | WebView | 小程序运行时 |
|---|---|---|
| 跨程式数据 | 单一共享 Web 上下文 | 每程式隔离存储 |
| 原生权力 | 敞开的 JS 桥 | 平台经手的特权 |
| 动态执行 | eval / 远程代码 | 在运行时层移除 |
逻辑层通常彻底移除 document、window 以及动态 eval 类执行,于是那条经典的"XSS 升级到原生"路径压根不存在。网络请求被强制走平台的域名白名单、走 HTTPS,于是静默外泄的通道被堵死。敏感 API——位置、相机、支付、文件——写进清单、在提交时审核、由平台经手,所以第三方程式无法悄悄拿走宿主从未打算共享的东西。
合规红利
正是这个属性,让"向伙伴开放 App"变得可投资。没有隔离,把第三方代码请进你最可信的表面,等于继承每一个第三方的所有风险与责任。有了隔离,宿主保留账户、钱包和通知通道,而每个小程序住在一个它逃不出去的围栏盒子里。内容安全、数据驻留、滥用防控,从"指望别出事"变成平台的职责。
它镜像了 OS 厂商已经在执行的同一原则:苹果的 App Sandbox 和 iOS 内核 MAC 策略给每个 App 一块私有文件系统区域,并在内核层阻断跨 App 访问——正是为了让一个脆弱或有恶意的 App 无法横向移动。小程序运行时把同样的纪律往下做了一层——不只是 App 对 App,而是同一个 App 内部程式对程式。
Cross Mini App 如何强制它
Cross Mini App 是作为一个以隔离为原语的通用运行时来构建的。每个小程序都有自己的沙箱执行边界、一个被能力门控的统一 wx.* API 表面,以及一条受治理的分发路径——提交审核、内容安全、滥用防控——在代码触达用户之前。写一次的程式能在每个装了运行时的宿主上跑,但它跑在墙内,绝不跑在墙外。
这就是"嵌入 Web"和"嵌入运行时"的区别:Web 带来敞开的门;运行时带来围栏——而围栏,才是让一个开放 catalog 可以安全运营的东西。