编程 Docker 沙箱不够:跑 AI Scientist 这类自动科研 agent 的隔离加固与 microVM 迁移

2026-10-03 00:04:03

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 不支持,需兼容性测试
FirecrackerKVM 硬件虚拟化,每实例独立 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,防止写入后直接执行

四个常见错误:

  1. 把 Docker socket 挂进容器。只要挂了 Docker socket,容器内进程基本就能控制宿主容器运行时。
  2. 把宿主 home 目录整块挂进去。
  3. 把云凭据放进环境变量。
  4. 用 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 边界上。

推荐文章

程序员茄子在线接单