AI 代理沙箱能拦住逃逸,但不会告诉你里面发生了什么

Rye Articles 2026-08-14T01:02:49.251285

Docker 刚刚发布了 Docker Sandboxes。它可以在专用的微型虚拟机(microVM)里运行 AI 编码代理,比如 Claude Code、Codex CLI、Copilot CLI、Kiro 和 OpenCode。这些 microVM 基于一个自研的 VMM(虚拟机监视器)构建,支持 macOS、Windows 和 Linux 跨平台运行。

每个沙箱有自己独立的内核、一个挂载的工作区,以及一份只允许访问已批准主机名的网络策略。在整个会话期间,工作区会实时挂载在宿主机的文件系统上。VM 状态(包括安装的软件包、shell 历史、VM 内写入的文件)在重启后仍然保留,只有显式删除沙箱时才会被丢弃。

对于以 YOLO 模式(--dangerously-skip-permissions)运行代理的开发者来说,这算是个实打实的改进。如果代理跑偏了,宿主机应该还是干净的。

但沙箱只能回答一个问题:代理逃出去了吗?

大多数团队首先需要的是另一个答案:代理在里面到底做了什么?

为什么容器从来都不够用

在 Docker Sandboxes 出现之前,通常的建议是把代理放进 Docker 容器里,只挂载项目目录,任务跑完就删除容器。听起来挺干净,可实际上没那么简单。

Docker 容器和宿主机共享同一个内核。命名空间(namespaces)和 cgroups 确实能起到一定隔离作用,但隔离边界仍然由同一个内核来强制。容器逃逸是一类反复出现的 CVE 安全问题:CVE-2019-5736 是 runc 覆盖,CVE-2022-0492 是 cgroup 逃逸,CVE-2024-21626 又是 runc。这些都不是什么简单攻击;但一个被提示注入(prompt injection)操纵的 AI 代理,不是我默认会信任的进程。

Firecracker microVM 改变了风险形态。每个沙箱运行自己的 Linux 内核,并通过 KVM 与宿主机隔离。客户机内核的 bug 不应该变成宿主机内核的 bug。这跟 AWS Lambda 和 Fly.io 机器采用的基本隔离模型是一样的:在共享硬件上运行不受信任的客户代码。

所以,隔离原语本身是对的。

问题出在边界守住之后。

沙箱实际能阻止什么

说得精确一点:带网络策略的 microVM 沙箱能缩小以下几类攻击面:

AI Agent 沙箱能防逃逸,却不会告诉你里面发生了什么

主机文件系统访问。 代理只能看到并写入明确挂载的项目工作区。它无法读取 ~/.ssh/~/.aws/credentials、你的 shell 历史记录,也无法读取你机器上的任何其他文件。

主机进程访问。 代理无法看到主机进程,也无法向它们发送信号。它不能给你的 IDE 附加调试器、不能杀掉你的 VPN 客户端,也不能干扰其他正在运行的代理。

网络横向移动。 有了“默认拒绝、仅白名单放行”的网络策略,代理就无法触达你的内部网络、云元数据端点(169.254.169.254),也不能随便访问互联网基础设施。它只能和经过你批准的域名通信。

VM 层销毁时的持久性。 当沙箱被明确移除时,VM 内安装的软件包、shell 历史记录,以及挂载工作区之外写入的文件都会被丢弃。工作区本身是实时挂载——整个会话期间,代理直接读写你的宿主机文件,而不是副本。VM 状态在重启之间保持,直到你执行 dispose 命令。

这些控制手段很重要,尤其是对无人值守的任务:夜间重构、持续集成(CI)代码审查代理、自动化测试生成。我更愿意让这些任务跑在微虚拟机(microVM)里,而不是开发者的笔记本上。

沙箱无法阻止什么

沙箱的边界就是微虚拟机的外围。边界之内,依然有一台忙碌的小机器在真实地干活。

你拿不到代理行为的任何审计日志。代理会读取文件、写入文件、执行 shell 命令、建立网络连接。沙箱并不会在代理自身的会话日志之外,以结构化日志的形式记录这些行为。那份日志在沙箱内部,由代理进程自己控制。如果代理在会话结束前把它删掉,这些记录也就跟着一起消失了。

代理仍然可能破坏项目。被提示注入(prompt injection)的代理可以删除工作区、覆盖配置、在挂载了凭据的情况下推送到 git 远程仓库,或者把源代码发送到白名单主机。沙箱阻止了它侵入宿主机,却没有阻止它使用你赋予它的那些权限。

网络允许列表是钝器。如果 api.github.comregistry.npmjs.orgpypi.org 被允许——而它们很可能因为实际开发工作而被允许——那么恶意安装或被入侵的远程端就拥有了一条有效的出站路径。允许列表能挡住随机攻击者的基础设施,却挡不住对理应开放的通道的滥用。

事后你无法重建发生了什么。如果沙箱会话产生意外输出、删除文件或提交惊人的代码,你没有内核级记录可供审计。代理的会话日志(如果存在且未被篡改)告诉你模型原本意图做什么,但不会告诉你系统调用层面实际执行了什么。

这就是盲点。Docker 沙箱可以完美履行其职责,却仍然让你无法解释一起事件。

eBPF 能看到什么

eBPF 听起来像内核冷知识,直到你需要一份可信的活动日志。它允许你将小型程序附加到内核事件上,例如系统调用入口和出口、网络连接以及文件系统操作。像 Cilium Tetragon 和 Falco 这样的工具利用它来为进程实际执行的操作生成结构化事件。

Diagram comparing eBPF visibility in Docker containers (host-side kprobes work, shared kernel) versus Firecracker microVMs (host kprobes stop at the KVM boundary; eBPF must run inside the guest).

容器(左):主机侧 eBPF 能看到代理发出的每个系统调用。microVM(右):KVM 硬件边界阻止主机 kprobes 看到 guest 路径、PID 或 argv。eBPF 必须运行在 guest 内部。

对于容器,从宿主机就能实现这一点。因为容器与宿主机共享内核,运行在宿主机上的 Tetragon 或 Falco 能看到代理发出的每个系统调用:

这不是采样。事件与内核事件同步生成,代理无法压制或删除它们——记录在系统调用返回之前就已写入。

对于microVM,情况则不同。客户机系统调用会陷入客户机内核,而KVM只会向宿主机暴露VM-exit。宿主机侧的kprobes和tracepoints看不到客户机内的VFS路径、文件名或PID——因为它们没有可挂载的共享内核。要在microVM内部获得同样的系统调用级遥测,eBPF必须运行在客户机内,或者你只能依赖VMM边界、工作区挂载层或沙箱运行时自身提供的钩子。只要遥测代理的权限高于工作负载,防篡改性依然成立,但部署起来比在宿主机上丢一个DaemonSet复杂得多。

完整审计轨迹长什么样

下面是我在Claude Code会话中希望看到的那种Tetragon输出——对于microVM由客户机内Tetragon生成,对于容器由宿主机侧Tetragon生成:

{"process": {"pid": 1847, "binary": "/usr/bin/node", "arguments": "claude --dangerously-skip-permissions"},
 "action": "open", "path": "/workspace/src/auth/session.ts", "flags": "O_RDONLY"}

{"process": {"pid": 1847, "binary": "/usr/bin/node"},
 "action": "open", "path": "/workspace/.env", "flags": "O_RDONLY"}

{"process": {"pid": 2103, "binary": "/bin/bash", "arguments": "npm install lodash-contrib"},
 "action": "connect", "destination": "104.16.1.35:443", "hostname": "registry.npmjs.org"}

{"process": {"pid": 2103, "binary": "/bin/bash"},
 "action": "execve", "path": "/workspace/node_modules/.bin/postinstall-hook", "arguments": ""}

{"process": {"pid": 1847, "binary": "/usr/bin/node"},
 "action": "open", "path": "/workspace/src/auth/session.ts", "flags": "O_WRONLY|O_TRUNC"}

从这条数据流中,事故调查的问题会变得清晰得多:

这些问题在事故排查中都很常见。可如果没有独立的事件流,沙箱一旦销毁,就再也没人能回答这些问题了。

Tetragon 与 Falco:实用的入手点

Cilium Tetragon 是解决这类需求的更强工具。它支持 TracingPolicy 资源,你可以自己挑选要追踪的事件:open、execve、connect、clone,进程二进制过滤,以及结构化 JSON 导出。对容器场景,它能以 DaemonSet 形态跑在宿主机上,也可以作为独立二进制运行;对 microVM 场景,则必须跑在客户机内部,也就是说要把它预置进 VM 镜像或沙箱启动脚本里。

一个面向 AI Agent 会话的最小化 Tetragon 策略示例:

apiVersion: cilium.io/v1alpha1
kind: TracingPolicy
metadata:
  name: ai-agent-audit
spec:
  kprobes:
    - call: "fd_install"
      syscall: false
      args:
        - index: 0
          type: int
        - index: 1
          type: "file"
      selectors:
        - matchBinaries:
            - operator: In
              values:
                - "/usr/bin/node"
                - "/usr/bin/python3"
                - "/bin/bash"
                - "/bin/sh"
    - call: "sys_execve"
      syscall: true
      args:
        - index: 0
          type: "string"
        - index: 1
          type: "string_array"
    - call: "tcp_connect"
      syscall: false
      args:
        - index: 0
          type: "sock"

如果环境里已经部署了 Falco,那它上手会更简单。它的规则语言更直白,默认规则也能捕获不少异常行为。代价则是细节。Falco 工作在更高一层,有可能会漏掉部分系统调用级别的上下文。对 AI Agent 这类工作负载而言,真正有价值的信号往往就是“读取了凭证文件”或“生成了可疑子进程”这类细节,这时候 Tetragon 多出来的信息通常值得你去花一点配置成本。

这给 AI Agent 安全栈留下了什么

microVM 沙箱和 eBPF 审计层分别解决威胁模型中的不同部分,应该组合使用:

这些防护层没有哪一层能独自扛下所有。没有遥测数据的沙箱就是个黑盒;只有遥测而没有隔离,你只能眼睁睁看着破坏发生。Agent 权限有帮助,但它跑在你想要监管的产品内部。没有审计记录的策略,事后就很难说得清。

查看原文