Agent 工程 · 第 6 章|执行隔离:威胁模型、隔离技术谱系、快照与回放

第 6 章 · 执行隔离与沙箱

Agent 能调用代码执行工具的那一刻,"模型生成的内容"就变成了"即将在你的基础设施上运行的代码"。执行隔离研究的问题是:如何让不可信代码在可控的牢笼里运行。

6.1 威胁模型

先明确要防什么——不是所有威胁都值得同等投入:

威胁 场景 防护优先级
模型写错代码误删数据 LLM 生成的 rm -rf 有路径错误 高(概率高、损失大)
Prompt injection 驱动的恶意执行 网页内容里藏"执行 curl evil.sh" 高(Agent 特有攻击面,第 11 章)
资源耗尽 死循环吃满 CPU/内存、fork 炸弹 高(必然发生,不是概率问题)
数据外泄 沙箱内代码把 workspace 文件 POST 到外部 中高(多租户平台必须防)
沙箱逃逸 攻击者利用内核/容器运行时漏洞逃到宿主机 低概率高代价(多租户强隔离场景才值得 Firecracker 级投入)

原则:误删与资源耗尽是必然事件,按工程问题解决;恶意逃逸按威胁建模分级投入。

6.2 隔离技术谱系

从弱到强,成本与启动时间同步上升:

进程级 + 语言运行时限制

Python 里用 resource 模块 / subprocess + ulimit 限制内存、CPU、进程数、输出量。

import subprocess, resource, shlex

def run_limited(code: str, timeout=10, mem_mb=256):
    limit = f"ulimit -v {mem_mb * 1024} -u 64 -t {timeout}; "
    return subprocess.run(
        ["bash", "-c", limit + shlex.quote(code)],
        capture_output=True, timeout=timeout + 2, text=True)

优点:启动毫秒级。缺点:与宿主共享文件系统、网络、内核——只能防误删与资源耗尽,防不了任何有意攻击。适用:完全可信代码的单机工具、开发环境快速迭代。

容器(Docker / K8s Job)

每任务一个容器:文件系统隔离(镜像层 + 挂载点白名单)、网络策略(egress 白名单)、资源配额(cgroup)、用户降权。

docker_cmd = [
    "docker", "run", "--rm",
    "--memory=512m", "--cpus=1",
    "--network=nothing",                      # 或 egress 白名单
    "--read-only",                            # 根文件系统只读
    "--cap-drop=ALL",                         # 丢弃全部 Linux capabilities
    "--security-opt=no-new-privileges",
    "-v", f"{workspace}:/work:rw",            # 只挂 workspace
    "agent-sandbox:latest", "python", "-c", code,
]

优点:隔离/成本/启动(秒级)的平衡点,K8s Job 化之后获得调度、回收、配额的全部生态。缺点:与宿主共享内核——容器逃逸漏洞(runc CVE 类)存在,恶意攻击者+内核漏洞的组合可以越过隔离。这是多数业务 Agent 的默认选择。

用户态内核(gVisor / Kata)

gVisor 在应用与内核之间实现一个拦截系统调用的"用户态内核",攻击面缩小到 gVisor 实现的 syscall 子集;Kata 用轻量 VM 隔离。代价:兼容性损耗(部分 syscall 慢或缺失)与运维复杂度。适用:多租户、运行不可信三方代码的平台。

microVM(Firecracker 等)

KVM 级硬件虚拟化的极简 VM(<125ms 启动、约 5MB 内存开销),每个任务一个独立内核。优点:硬件级隔离 + 快照(snapshot)能力——把 VM 内存态存盘、之后恢复,实现:

  • 确定性回放:快照 + 固定输入 = 可复现的执行(评测与 RL 轨迹的基础设施,第 9/13 章依赖);
  • 预热池:从"预加载了运行时"的快照恢复任务,冷启动从秒级降到毫秒级。

代价:需要 KVM 宿主与专职的 worker 管理面(网络命名空间、jailer、mTLS 通道)。适用:强隔离需求 + 快照/回放需求的平台。

选型决策树

代码来源是否完全可信? → 是:进程级限制即可
    ↓ 否
多租户/面向外部用户? → 否:容器(K8s Job)
    ↓ 是
是否需要快照/确定性回放或最强隔离? → 否:gVisor/Kata
    ↓ 是
Firecracker microVM

6.3 Workspace:数据面的隔离

进程隔离之外,数据面的隔离同样重要——Agent 的文件操作应该被约束在一个显式的 workspace 内:

  1. 路径解析在服务端:工具收到相对路径,服务端拼 workspace 前缀。绝不让客户端传绝对路径(../ 逃逸、任意文件读都从这里来);
  2. workspace 是唯一的可写区:容器根文件系统只读,唯一 rw 挂载是 workspace;
  3. 回写与检查点:workspace 内容要有版本化快照(每次任务结束或定时 checkpoint),误删可恢复——Agent 删文件不是 if 而是 when;
  4. 生命周期归属:workspace 按用户/Agent 派生与清理(挂到明确的 owner 上,不能成为无主存储的垃圾场)。

6.4 快照与确定性回放(进阶)

快照是高级沙箱的核心能力,三个工程要点:

  • 快照一致性:执行后把 workspace 回写到事实源(对象存储/DB),必须确认写入成功后才能推进快照代次(epoch)——回写未确认就推进,崩溃恢复时会出现"VM 内存态指向不存在的文件"的幽灵状态;
  • ** fencing(止步证明)**:旧实例恢复后必须证明自己已失效(epoch 过期)才允许继续操作,否则同一 workspace 出现两个"活着的"执行者;
  • 确定性是有代价的:快照回放要求固定输入、关闭随机性、网络行为可 mock——这是评测基础设施(第 9 章)与 RL 环境(第 13 章)专用的高投入形态,普通业务 Agent 用不到。

6.5 失败语义

沙箱系统的失败矩阵必须显式设计("分配失败时怎么办"):

场景 正确行为
分配超时/失败 fail-secure:任务失败并明示原因,绝不静默回退到无隔离执行
执行超时 杀进程/回收容器,返回超时错误给模型(可自救:换方法)
OOM 同上;若频繁发生,注入"输出太大,请分步处理"的提示
沙箱空闲 延迟回收(连续 N 秒无执行才销毁),平衡复用与成本
平台故障 归还给编排层(Job TTL、回收策略),不依赖业务代码清理

fail-secure 值得单独强调:隔离是安全边界,安全边界的降级必须是显式决策而不是异常处理的副作用。"k8s 分配失败所以先在本机跑一下"这类代码是生产事故的经典起点。

实现作业

  1. 给第 2 章的 Agent 加代码执行工具:先用进程级限制(6.2)实现,跑通"让 Agent 写一段代码并执行它";
  2. 改造成 Docker 版本(上面的 docker run 参数模板),对比两者的逃逸面差异,写一份 200 字的对比笔记;
  3. 构造三个攻击用例验证你的沙箱:读 /etc/passwd(应失败:根文件系统只读+无挂载)、curl 外部地址(应失败:无网络)、fork 炸弹(应失败:进程数 ulimit);
  4. 思考题:如果你的平台要给 1 万个外部用户跑"AI 数据分析"(上传 CSV,Agent 写 pandas 代码),你选哪级隔离?逐条列出你的依据。

深入材料

  • Firecracker 官方文档(docs.firecracker-microvm.org,快照与 jailer 机制)
  • gVisor 架构文档(gvisor.dev,syscall 拦截的设计)
  • OWASPGenAI 安全清单(与第 11 章衔接)
← 返回资讯列表

读者留言

COMMENTS 暂无
仅本站原创文章开放留言 · 请勿留下手机号、邮箱等个人信息

还没有留言,来说第一句?