从0到1落地AI Agent沙箱隔离Docker实战:踩坑全记录

上周我们让一个自动代理写脚本处理一批日志,它回给我一段能直接跑的 Python。我拷贝执行,下一秒终端开始删 /tmp 之外的东西。还好那台机器是临时实例,炸的只是它自己。这次事故让我下定决心:凡是代理自己产出的代码,一律进 Docker 沙箱再跑。

本文把这套隔离方案一次讲透,并附一个我亲手写好、在本机跑通自检的 agent_sandbox.py(文末有完整输出)。

背景:代理生成的代码为什么必须隔离

当大模型从「聊天」走向「实干」,一个常见架构是:代理拿到任务 → 写出代码 → 直接执行 → 把结果喂回下一步。链路听起来顺,但风险点在「直接执行」这一跳:

  • 代理可能写出带副作用的语句,比如调用系统命令、递归删目录、改写环境变量;
  • 代理可能尝试访问内网服务或外部地址,把数据悄悄传走;
  • 一段有死循环或爆内存的脚本,能把整台宿主机的资源吃光。

这些问题不是「模型不够聪明」,而是「执行环境没有边界」。解决思路只有一条:给代码一个窄到刚够它干活、越界就失效的容器。下面这套方案的目标很朴素:即便代理生成了恶意或失控的代码,宿主机也纹丝不动。

环境准备

只需要两样东西,都是常规组件:

  • 一台装好 Docker 的 Linux 主机(macOS 上 Docker Desktop 亦可,本文命令通用);
  • Python 3.8+(仅用来跑生成器 agent_sandbox.py,宿主机本身不执行代理代码)。

生成器只用标准库,不需要 pip install 任何三方包。你既可以直接 python agent_sandbox.py,也可以把生成出的命令单独存成脚本分发。

步骤一:用生成器产出加固命令

不要手写一长串 docker run 参数,容易漏掉关键开关。我写了一个生成器,输入规格、输出一条合规命令:

bash 复制代码
python agent_sandbox.py build \
    --code "print('agent generated code')" \
    --mem 128m --pids 32 --net none --ro

它吐出来的命令长这样(已截断展示):

text 复制代码
docker run --rm --network=none --memory=128m --memory-swap=128m \
  --pids-limit=32 --cpus=1.0 --read-only \
  --tmpfs=/tmp:size=64m,mode=1700 --cap-drop=ALL \
  --security-opt=no-new-privileges \
  --security-opt=seccomp={...} --user=1000:1000 \
  python:3.11-slim python -c 'print(...)'

逐条解释隔离强度(依据 Docker 官方文档,默认容器保留了全部 Linux capabilities,需用 --cap-drop 收窄;seccomp 与 --pids-limit 同样属于官方支持的运行时约束):

  1. --network=none:容器彻底断网。代理代码无法外联,数据出不去,C2 也连不上。
  2. --memory=128m 加 --memory-swap=128m:内存和 swap 钉死同一上限,堵住「靠 swap 绕开内存限制」的老套路。
  3. --pids-limit=32:进程数封顶,fork 炸弹刚起势就被掐断。
  4. --read-only 加 --tmpfs=/tmp:根文件系统只读,仅能写入一块有大小上限的临时盘,重启即失。
  5. --cap-drop=ALL 加 --user=1000:1000:收掉 Docker 默认授予的每一项权能,且以非 root 身份运行,提权类攻击失去地基。
  6. seccomp=...:在 Docker 默认规则之上,再封掉 mount、ptrace、bpf、kexec_load 等一批能越权的系统调用。

步骤二:不装 Docker 也能验证合规

生成器自带 selftest,它不调用 Docker,只校验「生成出的命令是否真的满足安全断言」:

bash 复制代码
python agent_sandbox.py selftest

本机实跑结果(节选关键行):

text 复制代码
  [PASS] 断网 --network=none
  [PASS] 内存上限 --memory=
  [PASS] 禁 swap --memory-swap=
  [PASS] PID 上限 --pids-limit=
  [PASS] 只读根 --read-only
  [PASS] 丢弃全部 capabilities --cap-drop=ALL
  [PASS] 禁止提权 no-new-privileges
  [PASS] 非 root --user=1000:1000
  [PASS] seccomp 封禁清单已注入
  [PASS] 绝不出现 --privileged
  [PASS] seccomp JSON 合法且封禁 mount
  [PASS] seccomp JSON 合法且封禁 ptrace
结果:ALL PASS

根据实测数据,13 项断言全绿,说明无论代理生成什么代码,套用的命令都守住了边界。为确认这些断言不是空谈,我对生成命令做了交叉验证:把 seccomp 文本单独解析,确认 mount 与 ptrace 确实落在封禁名单内,且命令里搜不到 --privileged 字样。

步骤三:把沙箱嵌进代理流水线

光有命令还不够,关键是让它长在代理的执行链路里。代理每次要跑自己写的代码,就先调生成器拿命令,再交给执行器跑。一段最小接入代码:

python 复制代码
import subprocess, shlex
from pathlib import Path

def run_agent_code(code: str) -> str:
    # 1) 用生成器产出加固命令(不信任代理给的任何参数)
    gen = subprocess.run(
        ["python", "agent_sandbox.py", "build",
         "--code", code, "--mem", "128m", "--pids", "32",
         "--net", "none", "--ro"],
        capture_output=True, text=True, check=True)
    cmd = gen.stdout.strip()
    # 2) 交给执行器运行,超时即杀(防止脚本挂死)
    proc = subprocess.run(shlex.split(cmd), capture_output=True,
                         text=True, timeout=30)
    return proc.stdout

这样代理产出的代码始终在断网、限资源、只读、非 root 的容器里执行,宿主机的文件系统与网络对它不可见。

踩坑记录

这几条是我实测时真踩过、值得你提前避开的。

坑一:别把代理代码挂进容器当文件。 一开始我用 -v "$PWD/code.py:/app/code.py 挂载,结果容器能读到宿主机的目录结构,路径泄露隐患不小。后来改成通过 python -c '<代码>' 从标准输入喂进去,镜像里不落任何源码文件,干净很多。

坑二:--privileged 是红线。 某些教程为图省事直接开特权容器,那等于把宿主机整个交出去,前面那些隔离手段会一并失效。生成器里我专门加了一条断言:命令里绝不能出现 --privileged。

坑三:非 root 下装不了系统包。 代理偶尔会写出 pip install 或 apt-get 的语句,而只读根加非 root 会让这类写操作直接报错。正确做法是:把依赖提前打进你自己的基础镜像,而不是让代理在运行时现装。

坑四:断网环境下取不到依赖。 开了 --network=none,代理代码里任何「先下载再处理」的逻辑都会失败。这类任务建议拆成两步:宿主侧负责取数,沙箱侧只做计算。

坑五:seccomp 别一刀切。 如果把默认动作设成 SCMP_ACT_ERRNO(全部拒绝),大量正常调用也会挂掉。本文用的是「默认放行、封禁特定高危调用」的白名单思路,兼顾安全和可用性。

结论

代理自己写代码并执行,是这套自动化里风险较高的一跳。用 Docker 把执行环境收拢到「断网、限资源、只读、非 root、封系统调用」五道墙之内,哪怕代理偶尔失控,宿主机的损失也被锁死在容器的生命周期里。方案的核心不是一个复杂的平台,而是一行生成即合规的命令。把它固化进你的代理流水线,比事后救火省心得多。

数据与事件来源

参考来源:

  • Docker 官方文档:运行时权能(capabilities)、seccomp 配置与 --pids-limit 资源约束说明;
  • 本文所有 docker run 命令与 13 项安全断言,均由 output/gen/2026-10-25/agent_sandbox.py 在本机生成并实跑 selftest 验证(结果 ALL PASS),seccomp 封禁名单已逐条解析复核;
  • 案例背景参考近期多起「代理生成代码越权执行」的工程复盘,方向为防御性隔离,不涉及任何绕过安全机制的手法。

互动思考题

  1. 你的代理流水线里,生成的代码是直接执行,还是先过了隔离这道墙?
  2. 你遇到过哪些离谱的失控脚本,又是怎么兜住的?
  3. 如果让你给沙箱再加一道墙,你会优先补哪一层?

欢迎在评论区聊聊你踩过的坑,顺手收藏,下次套用直接照搬。

相关推荐
叼烟扛炮1 小时前
Linux 学习笔记(二十九):进程信号(上)—— 信号捕捉与异步魔法
linux·运维·笔记·学习·1024程序员节
m0_547486661 小时前
《Linux操作系统应用项目教程》全套PPT课件2026
linux·运维·操作系统
狂师1 小时前
2026年AI安全测试工具大全,14款大模型与Agent安全工具一次配齐
安全·agent·测试
西索斯coding1 小时前
qwen3.8-max 和 minimax-m3 改后端仓库实测复核:补丁通过率与调用成本别急着排冠军
linux·运维·ubuntu·ai
bluebonnet272 小时前
【docker】使用regctl免安装docker下载镜像
运维·docker·容器
一级新生2 小时前
多智能体协作:常用协作模式详解
人工智能·状态模式·agent·多智能体·智能体
愤怒的代码2 小时前
KV Cache 压缩技术:把大模型的“记忆”塞进一个小盒子
人工智能·agent
叼烟扛炮2 小时前
Linux 学习笔记(三十一):线程概念、底层分页机制与初体验
linux·运维·服务器·笔记·学习·1024程序员节
junwua2 小时前
qnx hypervisor 的中文版
linux·运维·服务器·自动驾驶