1. 为什么我坚持给所有 Agent 业务套一层沙箱先说个真事。之前给某客户做智能客服升级计划让 Agent 自动调用工单系统、CRM、排班 API 去完成改地址查积分转人工这类操作。模型本身表现很不错工具调用准确率能到 93% 上下。结果在联调阶段一个测试任务里 Agent 意外调用了内部运维接口幸好那个接口当时处于 mock 状态不然一次真实的误操作就会把生产环境某个表的数据给清了。那之后我立了个规矩凡是跑 Agent 的环境必须有一层明确的沙箱边界没有例外。这里要解释一下AI Agent 和普通程序本质上有很大不同。普通程序行为是确定的输入什么逻辑就执行什么逻辑出问题可以靠 code review 和单测兜底。但大模型驱动的 Agent 是个概率系统同一个 prompt 给两次执行路径可能完全不一样而且它会自己拆解目标、选择工具、构造参数。它自己都不知道自己下一步要干嘛代码审查根本无从审起。所以只能用运行时隔离来兜底——也就是沙箱。沙箱对 Agent 的价值主要体现在四块环境隔离Agent 跑在一个独立、可销毁的环境里宿主和平台核心服务不受影响。权限收敛Agent 进程只有完成任务所需的最小权限拿不到宿主主机的权限。网络管控外网访问、内网访问都走白名单禁止默认放开。审计留痕Agent 的所有操作记录可还原、可回放、可归档。我现在的做法是所有 Agent 任务无论大小统一跑在受控沙箱里。评测任务放沙箱生产任务也放沙箱只是安全策略等级不同。这个习惯帮我拦下了几次风险事件后面你会看到具体案例。2. 沙箱粒度怎么定容器、微虚拟机还是纯进程限制选沙箱技术方案时我对比了市场上主流的三种路线容器沙箱、微虚拟机microVM、纯进程级限制。它们不是互斥关系很多线上系统其实是混用的。2.1 三条技术路线的核心差异为了讲清楚我直接列了一张对比表方便你按场景选维度容器Docker/containerd微虚拟机gVisor / Kata / Firecracker纯进程限制seccomp / namespace启动速度毫秒级非常快百毫秒到秒级微秒级接近零开销隔离强度内核共享存在逃逸风险独立内核或系统调用拦截强度最高仅隔离系统调用不隔离进程本身资源开销低中等到偏高极低适用场景大量快速任务、评测、批处理高安全要求、运行不可信代码对性能极其敏感的内核态场景运维复杂度低生态成熟中高需要额外组件高需要精细配置我见过不少团队一上来就追求最强隔离直接上 Firecracker 微虚拟机。但实际跑起来发现一个问题Agent 任务大多是短平快的一次工具调用可能三秒就完成了微虚拟机的启动开销反而拖慢了整体吞吐。所以我的建议是隔离强度要跟任务的信任级别匹配别一刀切。2.2 我最终采用的方案容器为主、gVisor 兜底我的默认方案是 Docker 容器每个 Agent 任务一个容器用完即销毁。对于信任级别低的任务比如让 Agent 执行自然语言描述里带出来的 shell 命令我会在容器里再套一层 gVisor 运行时作为加固。为什么不用纯 Docker因为 Docker 容器和宿主机是共享内核的一旦出现容器逃逸漏洞宿主就没了。gVisor 的价值在于它在用户态实现了系统调用拦截即使 Agent 在容器里执行了恶意代码也只能触达 gVisor 这个假内核碰不到宿主机真实内核。一个更省事的做法是直接用runsc作为 Docker 的 runtimedocker run --runtimerunsc \ -it --rm \ --networknone \ -v /tmp/agent-workspace:/workspace:rw \ agent-runtime:latest这样每个 Agent 都跑在一个受限运行时里系统调用被拦截网络默认关闭工作目录被挂载成一个临时目录。任务结束以后容器直接删掉什么都不会留下。2.3 什么场景下我才会上微虚拟机经验是当 Agent 需要处理客户提供的不受信任文件比如 PDF、压缩包、Office 文档时我会上 Kata 容器或 Firecracker。因为解析文档类操作最容易踩到内核漏洞或解析器漏洞恶意构造的文件可能在解析阶段就试图逃逸。这种情况我会把 Agent 连同文档解析进程一起放进微虚拟机彻底隔离。当然微虚拟机也有代价启动慢、资源占用高、调优复杂。所以它只用于高风险文件处理这一类任务不会每个对话都开。3. 网络出口管控默认禁网白名单放行审计每一字节流量Agent 沙箱最容易忽略的就是网络隔离。很多人只做了文件系统隔离却忘了给 Agent 一个完全受控的网络环境。事实上Agent 最大的风险面恰恰是网络出口。3.1 为什么网络要跟沙箱同级对待你想想一个没有网络限制的 Agent 能做什么它可以调用任意公网 API、可以访问内网管理系统、可以把数据 POST 到某个第三方服务。模型确实有对齐训练但提示注入、恶意指令、上下文攻击都能让 Agent 变成攻击工具。所以我的网络策略很简单分三层默认禁止一切网络连接。容器创建时--networknoneAgent 进程根本拿不到网络设备。按需开通白名单域名/IP。通过一个本地代理统一放行只有 Agent 实际需要调用的外部服务才进白名单。内网访问一律走网关鉴权。即使开了内网权限请求也要经过 API 网关能精确到工具级别和控制级别。3.2 一个可落地的网络放行配置我在生产环境里用一个 sidecar 代理统一收口 Agent 的所有 HTTP 请求。Agent 自身不直接访问外部而是把请求发给本机代理由代理根据策略决定放行还是拦截。大致配置长这样# agent-net-proxy.yaml rules: - name: public-internet domain: [api.external-service.com, www.company-cdn.com] action: allow rate-limit: 10/s - name: internal-apis domain: [api.internal-tools.example.corp] paths: [/v1/ticket, /v1/customer] methods: [GET, POST] action: allow-http-gateway - name: catch-all action: deny log: trueAgent 调用工具的 URL 必须匹配规则才能出去。比如客户服务 Agent 需要查工单它就只能访问工单 API其他一律拦截。通过 API 网关做内网访问鉴权时还会带上调用方标识和链路 ID一旦某个 Agent 行为异常能精确定位到是哪个任务发的请求。3.3 DNS 层面也要管住很多团队只做了 HTTP 层白名单但 Agent 完全可以绕过 HTTP 走 DNS 隧道或者直接发 UDP 包。所以在沙箱内部我把 DNS 解析也收编了容器内的/etc/resolv.conf指向本地 DNS 代理只有白名单域名才能被解析其他域名直接 NXDOMAIN。这样即使 Agent 想外连也找不到可用的出口路。4. 资源限额与熔断降级别让一个失控 Agent 拖垮整个平台沙箱不是只管能不能跑还得管能跑多久、能用多少资源。Agent 是自主循环系统一个模型如果陷入死循环或者在某个工具上反复重试它就会无限消耗 CPU、内存、网络流量。每个请求都计费每次重试都延时这种失控会直接拖垮整个服务。4.1 硬性配额怎么设我一般给 Agent 容器设四个维度的配额CPU 会限、内存要够、磁盘给大、超时必须短。下面是 docker run 里的参数docker run \ --cpus1.0 \ --memory1g \ --memory-swap1g \ --pids-limit128 \ --read-only \ --tmpfs/tmp:rw,size256m,mode1777 \ --stop-timeout10 \ agent-runtime几个容易被忽略的细节--memory-swap要和--memory相同否则容器可能通过 swap 把自己卡死。--pids-limit限制了进程数防止 Agent fork 炸弹或者启动一堆子进程。--read-only挂载根文件系统Agent 只能写 tmpfs 和工作目录其他地方改不了。--stop-timeout10控制 SIGTERM 之后的清理时间防止容器永远不退。除了容器层配额平台层面还会对单次 Agent 运行的总时长设上限。我这边默认是 5 分钟看任务复杂度也可以放宽超过就发 SIGTERM再一分钟没反应就 SIGKILL。这样即使模型陷入死循环也最多占用 6 分钟的资源。4.2 工具调用层的熔断容器层的配额只能兜底真正防失控还需要工具调用层的策略。我设计了三个熔断条件任一触发就终止当前 Agent 任务连续失败熔断同一个工具连续调用超过 5 次失败立刻停止任务。结果漂移熔断Agent 的输出偏离任务目标超过阈值用 embedding 相似度衡量立即取消。评价分骤降熔断如果平台有自动评价机制分数从高位跌到最低分且无法恢复则终止。标签上这仨有个场景有个 Agent 在不停调用定时任务 API每次传的时间格式都错失败了就换一种格式重试。如果没有连续失败熔断它会烧十分钟。加了熔断后第五次失败就停了非常果断。4.3 退避与降级策略除了熔断还要有降级策略。比如 Agent 请求某个上游接口时若上游返回 429 或 5xx不要立即重试而是按指数退避第一次等 1 秒第二次 2 秒第三次 4 秒最多到 30 秒。同时可以设置一个允许总等待时间超过就直接跳过该工具避免一次慢查询把整个任务拖垮。这类降级逻辑写进 Agent 框架里比在模型 prompt 里加约束可靠得多。纯粹靠提示词告诉模型别重试是没用的模型该重试还是重试。5. 可观测性与事后复盘把 Agent 每一步操作完整还原沙箱的最后一个核心功能是留痕。AI Agent 出问题的时候你不能只靠看模型输出判断哪里错了。必须能回答它调了哪些工具、传了什么参数、请求了哪些 URL、读了哪些文件、用了多长时间、每一步的中间结果长什么样。这些信息要全部记录且要能快速回放。5.1 日志体系记录哪些内容我设计的 Agent 审计日志至少包含这几个维度类别内容示例任务元信息任务 ID、模型版本、提示词版本、超时时间task_abc123,gpt-4o-2024-08工具调用工具名、参数、返回值摘要、耗时工具名ticket_api, 参数{id: 123}, 耗时350ms网络访问目标域名、请求路径、方法、状态码POST api.internal-tools/customer, 200文件操作读取/写入的文件路径、大小读 /workspace/upload/xx.pdf, 2MB系统行为进程执行记录、异常退出sh -c curl ...关键决策Agent 选择了哪条分支、打分评价选择了方案B, 因为 ...每条日志都带上任务链路 ID后面就可以一键追溯整个任务的生命周期。5.2 一次恶意文件事件的完整复盘链路举个真实复盘例子。某次评测中一个 Agent 在处理一个恶意构造的 PDF 时触发了异常行为。我在日志里看到的时间线是这样的09:01:02 任务启动 09:01:13 读取 upload/evil.pdf (2MB) 09:01:28 调用 pdf_parser 工具 09:01:41 进程异常退出 (exit code 137) 09:01:42 容器被 gVisor 拦截一次 syscall 09:01:43 网络代理请求被 deny 09:05:00 任务超时被平台终止整个过程中文件解析进程确实尝试执行了一次系统调用被 gVisor 拦了随后它又尝试外连一个陌生域名被网络代理 deny。虽然我们没有成功拦下文件自身的恶意行为因为 pdf_parser 本身被构造的文件坑了但沙箱防线阻止了后续所有危害动作。最后容器销毁恶意文件没有落地到宿主。这种复盘能力没有沙箱日志是绝对做不到的。普通程序最多留个报错信息Agent 的问题往往是一个长链条里的多个环节叠加你必须能一条一条捞出来看。6. 落地过程中的五个坑快照、Docker in Docker、清理时机与更多沙箱落地过程中踩过不少坑选几个典型的分享一下。你要是打算在自己的平台里搞同类系统这些大概率你也躲不开。6.1 坑一容器根文件系统--read-only和依赖库之间的冲突Agent 跑起来要装 Python 包、要写临时缓存这些都需要写/usr或/root/.cache。我一开始直接挂--read-onlyAgent 任务全在初始化阶段就崩了。后来解决方案是提前把依赖烘焙进镜像并把缓存目录挂到 tmpfs。另一个有效技巧是分两层挂载根文件系统只读但单独挂一个可写的agent-cache目录。这样既保护了镜像文件又给了 Agent 足够的缓存空间。6.2 坑二Docker in Docker 的权限陷阱有段时间我想让 Agent 在沙箱里再管理别的容器于是用了 docker-in-docker 方案。结果发现 mounted docker.sock 等于把宿主机 Docker 的 root 权限直接交给了 Agent——这比不沙箱还危险。后来改成两个方案需要嵌套调用容器的场景直接通过外部编排 API 让 Agent 请求一个容器管理工具工具在宿主侧执行Agent 拿不到任何 Docker socket 权限。利用这个思路Agent 的权限始终是最小化的它只能请求创建容器这类操作而真正执行的是宿主上的受控服务。6.3 坑三容器退出后的文件残留与磁盘泄漏最开始我是容器跑完直接docker rm结果发现宿主机磁盘越用越少。查了半天发现容器产生的大量日志、缓存、临时文件都写到了挂载卷里容器删了但卷没删。后来我在编排层统一做了三件事挂载卷全部使用临时卷不写持久化路径、容器销毁时一并删掉关联卷、定时任务扫描超过 24 小时的孤儿卷并清理。6.4 坑四超时后容器不退出容器配了--stop-timeout10但 Agent 任务本身就是多线程的主进程收到 SIGTERM 后子进程还会继续跑。更糟的是有些 Agent 框架捕捉到 SIGTERM 后会自己收拾半天才退出10 秒根本不够。我的处理是在任务级别增加一个强制终止通道超时先让 Agent 框架执行善后保存状态、补日志2 秒后下发 SIGKILL同时用nsenter或进程组 id 把所有子孙进程一并杀掉。这样容器一定是干净退出的。6.5 坑五快照时机不对导致评测结果失真做 Agent 评测时我们会在任务开始前给工作目录打一个快照用来对比 Agent 改动了哪些文件。最开始我是在容器启动那一刻打快照结果任务一完发现快照不完整——因为 Agent 可能把一些预置数据也删了或改了对比完全失真。后来我把快照时机调整为基础镜像 本次任务专属数据两层基础镜像永不改任务数据单独挂载并记录 hash评测时只比对任务数据的变化非常干净。7. 一点个人体会说回最初那个客户项目的教训。回想起来我们当时没把沙箱当安全衬底来设计而是当成上线前再补的加固这个顺序本身就是错的。AI Agent 的行为天然不可预测只有在不可预测性发生之前就把它装进笼子里才能保证后面的评测、上线、灰度都是安全的。如果你现在刚开始搭自己的 Agent 平台我的建议是先不要纠结哪个沙箱技术最先进先想清楚你的 Agent 会接触什么、能做什么、可能会搞坏什么然后从最小权限出发补全隔离策略。容器、gVisor、网络代理、资源配额、熔断、审计日志这六块缺哪块都不完整。等这些基础打牢再谈 Agent 的能力上限才有底气。