Docker 沙箱不够:跑 AI Scientist 这类自动科研 agent 的隔离加固与 microVM 迁移
AI Scientist 的 README 先说清了风险
SakanaAI/AI-Scientist 的 README 里有一段警告,含义是:这个代码库会执行 LLM 写出来的代码,随之而来的风险包括使用可能有危险的包、无控的联网访问,以及 spawn 进程;使用需自行判断,务必容器化并适当限制网络访问。
v2 同样是这个模型。它的 tree search 组件基于 AIDE 项目:WecoAI/aideml。
也就是说,沙箱在这里不是可选项,而是运行前置条件。问题在于:容器本身算不算够。
SandboxEscapeBench:注入漏洞后,前沿模型能识别并利用逃逸路径
SandboxEscapeBench(arXiv:2603.02277,ICML 2026 poster) 的标题是 Quantifying Frontier LLM Capabilities for Container Sandbox Escape。
背景是:LLM 越来越多作为自主 agent 使用工具、执行代码、读写文件、访问网络。为了缓解风险,agent 常被部署在隔离的 sandbox 中,而 Docker/OCI 是其中最流行的容器 runtime 之一。
作者提出的 SandboxEscapeBench 是一个开放基准,用来度量 LLM 逃出这些沙箱的能力,用 Inspect AI 的 CTF 评测实现,采用嵌套沙箱架构:外层放 flag,且没有已知漏洞。威胁模型是「容器内已经拿到 shell 访问权的有动机对抗 agent」。覆盖的逃逸机制横跨四类:
- 配置错误(misconfiguration)
- 权限分配错误
- 内核缺陷
- 运行时/编排弱点
结论是:当注入漏洞时,LLM 能识别并利用它们。要确保沙箱继续为高能力模型提供所需封装,这类评测是必要的。
UK NCSC 的指南给出了同样的方向:标准容器 runtime 的内核强制隔离,通常不足以单独构成强安全边界,建议做纵深防御——hypervisor 隔离应作为最低要求,在其中再做容器隔离,同时去掉不必要的网络等权限。
对 agent 开发者来说,默认把「纯 Docker 隔离」当作够用的边界,是不成立的。
共享内核是结构性风险:几种隔离机制对照
Docker 默认用 runc 运行时,容器与宿主共享内核。LLM 生成的代码可以通过内核漏洞、设备挂载或 seccomp 绕过逃逸到宿主机。共享内核是结构性风险,不是靠打补丁能完全弥补的。
| 机制 | 隔离边界 | 启动与开销 | 注意点 |
|---|---|---|---|
| Docker(默认 runc) | 与宿主共享内核 | 容器级启动 | 内核漏洞、设备挂载、seccomp 绕过都可能导致逃逸 |
| gVisor | 用户态 syscall 拦截 | 额外延迟约 50–100ms | 无需 KVM,部分 syscall 不支持,需兼容性测试 |
| Firecracker | KVM 硬件虚拟化,每实例独立 guest kernel | 冷启动约 125–300ms,snapshot restore 可 <5ms,内存约 5MB/实例 | 平台复杂度高 |
| Kata | 每容器独立内核(VM 级) | 资源开销更大 | syscall 兼容性比 gVisor 更好,适合保留 K8s 调度的严格隔离场景 |
| E2B | 托管服务,基于 Firecracker | 几行代码接入 | 有 Agent 集成 SDK |
gVisor 是 Google 开源的 syscall 拦截沙箱,以 OCI runtime 形式集成 Docker/containerd/Kubernetes,用 --runtime=runsc 即可切换,不需要 KVM,与 Docker 生态兼容。代价是部分 syscall 不支持,上线前需要做兼容性测试。
Firecracker 是面向 serverless/多租户的轻量 VMM,基于 KVM 启动 microVM,每个实例有独立 guest kernel(vmlinux),隔离边界从「共享宿主内核」提升到「硬件虚拟化边界」。它只暴露最小化设备模型(virtio-net、virtio-blk)。jailer 会在启动 guest 之前把 VMM 自身放进 chroot、PID/network namespace,并做降权和 seccomp-BPF 沙箱。代价是平台复杂度:要管镜像、快照、内核、网络、磁盘、启动池、日志、VM 回收、漏洞补丁。
选型分歧点通常落在三件事上:能否用托管服务、是否需要 GPU、要多强的隔离。
Docker 加固:参数清单
如果暂时停在容器这一层,至少把下面这些参数配齐。
docker run \
--network none \
--read-only \
--memory \
--cpus 0.5 \
--pids-limit 30 \
--security-opt no-new-privileges \
--tmpfs /tmp:noexec \
sandbox-image
- 禁网:
--network none - 只读 FS:
--read-only,防持久化 - 内存限制:
--memory,防 OOM 炸宿主 - CPU 限制:
--cpus 0.5 - 进程数:
--pids-limit 30,防无限循环和进程炸弹 - 无新权限:
--security-opt no-new-privileges,防 setuid 提权 - 非 root:镜像里
USER sandboxuser,减小逃逸后的影响范围 - 临时空间 noexec:
--tmpfs /tmp:noexec,防止写入后直接执行
四个常见错误:
- 把 Docker socket 挂进容器。只要挂了 Docker socket,容器内进程基本就能控制宿主容器运行时。
- 把宿主 home 目录整块挂进去。
- 把云凭据放进环境变量。
- 用 privileged container 解决兼容性问题。只要开了 privileged,很多 namespace、seccomp、AppArmor/SELinux 约束都会被削弱或绕过。
超时是资源保护,不是安全控制。30 秒足够完成一次 curl 数据外传,或者一次内网扫描。
command broker 方面:不要把模型生成的 shell 字符串直接交给宿主 shell,应由 broker 统一设置工作目录、环境变量、超时、输出上限、进程组、sandbox profile 和审计字段;所有子进程必须继承同一套限制。
Code Airlock:一次性 microVM + git 回审
code-airlock 的做法是在一次性 microVM 里无人值守跑编码 agent(Claude Code、Codex、OpenCode),工作以 git commit 形式提交回来供宿主审阅。它是 Docker Sandboxes 的一层小包装。
- 默认 clone 模式:宿主仓库以只读挂载,agent 在沙箱 VM 内的私有 clone 里改并 commit。
- 回审:fetch/diff/review/merge 沙箱分支,满意再合并。
- 网络:可配置 allowlist(模型 API、包仓库),lockdown 会把默认网络策略设成 deny-all。
- 边界是 Docker Sandbox 的 microVM,而不是 agent 自己的权限模型;harness 规则是第一层,沙箱是下面的边界。
- 别给沙箱宽泛凭据,allowlist 尽量窄;允许的 host 仍然是可能的出口路径。
把这几块拼起来看:Docker 加固能收紧一层,但共享内核这件事不会因此消失;要换掉这个结构性前提,就得把隔离边界挪到 gVisor 的 syscall 拦截,或者 Firecracker/Kata 这类每实例独立内核的 VM 边界上。