漏洞链其实很短
Prefab UI 的 Generative UI 功能会把调用方给的 Python 代码放进 Pyodide(编译成 WebAssembly 的 Python),再在 Deno 子进程里跑。问题在于启动参数:0.14.0 到 0.20.2 用了不受限的 --allow-read、--allow-write、--allow-net,同时 runner 没有把 Deno API 从 JavaScript 全局对象上移除。Pyodide 的 js 模块能代理到这个全局对象,于是 Python 代码可以直接调用 Deno.readTextFileSync 和 Deno.writeTextFileSync。
常见的远程路径是 FastMCP 的 GenerativeUI provider:它注册一个 generate_prefab_ui 工具,把工具参数里的 code 原样送进沙箱,没有额外检查。未配置认证的 FastMCP 服务器,任何能访问端点的人都能调用;配了认证,任何被允许调用工具的人也能。JFrog 给的未认证评分是 10.0,已认证是 9.9。
为什么能持久化
攻击者不只读写文件。在 0.20.2 上,JFrog 演示了往服务器虚拟环境的 site-packages 写一个 .pth 文件:下一次有 Python 进程使用这个环境,就会执行植入的 import。也就是说,即使容器重启、扩展升级,后门仍可能跟着环境走。修复版 0.21.0 的做法是:在运行环境和 Prefab 源码加载完成后、接受代码之前,撤销 Deno 的读、写、网络权限,并用 --no-prompt 防止后续再申请;启动阶段的联网只允许 cdn.jsdelivr.net:443。
如果你在用,按这个顺序处理
pip install -U 'prefab-ui>=0.21.0'
python -c 'import prefab_ui; print(prefab_ui.__version__)'
# 查有没有陌生的 .pth
python -c 'import site,glob,os; [print(p) for d in site.getsitepackages() for p in glob.glob(os.path.join(d,"*.pth"))]'- 升级到 0.21.0 或更高;升级后重启所有使用该环境的进程。
- 如果不用 Generative UI,直接不注册 GenerativeUI provider,别为了演示把工具留在公网。
- 必须用时,加认证与调用方白名单,把服务放进容器,根文件系统只读,默认断网,只放行必要的 CDN。
- 审计 site-packages 里的 .pth、最近修改的 .py 与启动脚本;有异常就重建环境,不要只删文件。
- 给沙箱加资源上限:CPU、内存、执行时间、输出大小,避免被拿来挖矿或打内网。
更大的教训
「让模型生成 UI」和「执行模型写的代码」是两件事。前者只需要渲染,后者需要真正的隔离:独立进程、最小权限、默认无网络、只读文件系统、可丢弃的环境。很多工具把两者做在一起,是因为演示方便;上生产时要把它们拆开。判断一个沙箱是否可信,不看文档里写没写「isolated」,看它启动子进程时给了哪些权限、跑完有没有撤销、失败时会不会静默降级。
沙箱的安全性不取决于它叫沙箱,而取决于子进程启动时那一行权限参数。