你让 AI 编程代理帮你清理项目里的临时文件,它理解成了"清理用户目录下的所有文件",然后毫不犹豫地执行了 rm -rf ~/。这不是段子,是真实发生的安全事故。AI coding agent 的权限边界如果没画清楚,它犯错的代价远比人类手滑更致命——因为它执行命令时没有犹豫。
一场从"清理临时文件"到"清空家目录"的灾难
事故的起因很普通:开发者给 AI 编程代理下了一个指令,大意是"把项目里不需要的临时文件清理掉"。代理理解指令后,构造了一条删除命令。问题出在路径上——它把 ~/project/tmp 理解成了 ~/,或者干脆把通配符的范围扩大到了整个家目录。命令执行完毕,家目录下的配置文件、SSH 密钥、工作文档、本地仓库全部消失。
这类事故的核心特征:代理构造命令的能力很强,但对语义边界的判断极弱。人类在敲 rm -rf 时会本能地反复确认路径;AI 代理不会,它只关心指令是否被"完成"了。
前文系列的第一部分已经梳理了 AI 编程代理的六类漏洞模式,包括路径穿越、命令注入、权限越界等。rm -rf ~/ 属于"路径理解偏差 + 权限无边界"的组合型事故,也是后果最直观的一类。
为什么"提醒代理小心"不是解决方案
有人可能会想:在 prompt 里加一句"删除文件前请确认路径"就行了。现实证明这不够可靠:
- 指令约束是软约束,代理可以忽略或绕过。 当任务目标与安全提示冲突时,代理倾向于完成任务。
- 代理的命令构造是隐式的。 你看到的是最终执行的命令,但中间的路径推理过程你无法逐帧审查。
- 一次失误就足够致命。
rm -rf不像git reset,没有内置的撤销机制。
真正有效的防线必须在执行层拦截,而不是在推理层祈祷。
Docker 沙箱:在执行层画一道硬边界
Docker 容器提供的是操作系统级的隔离。即使代理在容器内执行了 rm -rf /,被摧毁的也只是容器自身的文件系统,宿主机的家目录毫发无损。这就是所谓的"workspace-scoped isolation"——代理的权限被硬性限制在工作空间范围内。
下面是一个可以直接拿来用的最小化沙箱方案。
1. 构建代理工作环境镜像
# Dockerfile.agent-sandbox
FROM python:3.11-slim
# 只安装代理需要的工具,不装多余的东西
RUN apt-get update && apt-get install -y --no-install-recommends \
git \
curl \
&& rm -rf /var/lib/apt/lists/*
# 创建非 root 用户,进一步缩小权限
RUN useradd -m -s /bin/bash agent
USER agent
WORKDIR /workspace
# 安装代理的 Python 依赖
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
COPY . /workspace
关键设计点:用非 root 用户运行,即使代理在容器内尝试 rm -rf /,非 root 用户也无法删除系统关键文件。
2. 用 Docker Compose 启动隔离工作空间
# docker-compose.yml
services:
coding-agent:
build:
context: .
dockerfile: Dockerfile.agent-sandbox
# 不映射宿主机家目录,只映射项目目录
volumes:
- ./src:/workspace/src:rw
# 只读挂载配置文件,代理能看但不能改
- ./config:/workspace/config:ro
# 禁止特权模式,限制能力
cap_drop:
- ALL
cap_add:
- NET_RAW # 如果代理需要网络访问
# 内存和 CPU 限制,防止代理跑出资源失控的命令
mem_limit: 512m
cpus: 1.0
# 禁止容器内新建特权容器
security_opt:
- no-new-privileges:true
networks:
- agent-net
networks:
agent-net:
driver: bridge
internal: true # 禁止访问宿主机网络
3. 运行代理并验证隔离效果
# 构建并启动沙箱
docker compose up -d coding-agent
# 在容器内模拟代理执行危险命令
docker compose exec coding-agent rm -rf /
# 检查宿主机家目录——应该完好无损
ls ~/ && echo "家目录安全"
# 查看容器状态(大概率已崩溃,但那只是容器)
docker compose ps coding-agent
# 重新启动一个干净的容器即可恢复
docker compose up -d coding-agent
运行前需要修改的地方:./src 和 ./config 换成你实际的项目路径;requirements.txt 换成代理实际依赖的包列表。
六类漏洞的沙箱对照
根据前文梳理的六类 AI 编程代理漏洞,Docker 沙箱在执行层能拦截的包括:
| 漏洞类型 | 无沙箱后果 | 沙箱内后果 |
|---|---|---|
| 路径穿越删除(rm -rf ~/) | 家目录被清空 | 容器文件系统被清空,宿主机无损 |
| 命令注入(代理执行了恶意 curl) | 宿主机被植入后门 | 容器内下载恶意文件,网络隔离阻断外联 |
| 权限越界(代理修改 /etc/hosts) | 系统配置被篡改 | 非 root 用户无权修改,cap_drop 阻断 |
| 依赖链攻击(代理安装了恶意 pip 包) | 宿主机 Python 环境被污染 | 污染仅限于容器内,重建即清除 |
| 凭证泄露(代理读取 ~/.ssh) | SSH 密钥被外泄 | 家目录未挂载,代理根本看不到密钥 |
| 资源耗尽(代理启动 fork 炸弹) | 宿主机 CPU 被打满 | mem_limit + cpus 硬性限制 |
沙箱不是万能药——比如代理在容器内构造了恶意代码并提交到远程仓库,这属于逻辑层事故,沙箱拦不住。但执行层的硬隔离能把最致命的那类物理破坏限制在可重建的范围内。
上线前的检查清单
在把 AI 编程代理接入真实工作流之前,逐项确认:
- 代理是否在 Docker 容器内运行? 如果直接在宿主机 shell 里跑,等于把家门钥匙交给了它。
- 容器是否以非 root 用户运行?
USER agent这一行不能省。 - 宿主机家目录是否被挂载进容器? 如果是,代理就能读到你的 SSH 密钥和私有配置。只挂载项目目录。
- 敏感挂载是否设为只读? 代理需要读取的配置文件用
:ro挂载,防止它篡改。 - 是否禁用了特权模式并 drop 了所有 capability?
cap_drop: ALL+no-new-privileges是底线配置。 - 是否设置了资源限制?
mem_limit和cpus防止代理失控消耗宿主机资源。 - 容器网络是否需要外联? 如果代理不需要访问外部 API,用
internal: true的网络彻底隔离。
最后一项容易被忽略:代理执行完任务后,容器是否被销毁? 长期运行的容器会积累状态,增加被利用的风险。用完即毁、下次从干净镜像重建,是最省心的策略。
# 任务完成后销毁容器和其文件系统
docker compose down -v
AI 编程代理是好工具,但好工具也需要好笼子。rm -rf ~/ 的事故已经发生了,下一个事故的形式可能不同,但本质一样:代理的执行权限没有边界。Docker 沙箱在执行层画的那道线,是你能部署的最廉价、最可靠的硬约束。