AI 编码代理不再只是补全几行代码。它可以打开终端、修改文件、执行部署命令,甚至使用云端凭据。来源案例中的代理误删了生产环境,最终造成长达 13 小时的中断。真正值得关注的并不是代理为什么会犯错,而是一个更基础的工程问题:为什么开发工具拥有足以摧毁生产环境的权限?
Docker 沙箱的价值正在这里。隔离执行环境、限定挂载目录,再为每次任务分配作用域明确的身份,可以把一次错误命令限制在可回收的容器内,而不是让它直接作用于开发者主机或生产账户。
事故暴露的不是提示词问题,而是权限模型问题
面对代理误操作,团队很容易把修复方向放在提示词上,例如加入“不要删除生产资源”或“执行危险命令前必须确认”。这些规则有帮助,但不能构成安全边界。
模型可能误解上下文,工具也可能返回不完整的信息。即使代理准确遵守提示词,仓库里的脚本、依赖安装钩子或被污染的输入仍可能触发危险操作。因此,系统必须假设代理最终会执行错误命令,并从权限层阻止命令越界。
一个编码代理通常接触四类高风险能力:
- 主机文件系统,包括 SSH 密钥、云配置和其他项目目录。
- Docker Socket;持有它通常近似于持有宿主机 root 权限。
- 长期云凭据,尤其是能同时访问开发、测试和生产环境的密钥。
- 不受限制的网络,可用于访问内部控制面或传出敏感数据。
只要这些能力被完整交给代理,“要求确认”就只是一条行为建议。可靠的控制必须让越权操作在操作系统、容器运行时或身份系统层面直接失败。
Docker 沙箱应该隔离什么
有效的沙箱不只是“把程序放进容器”。容器仍然可能挂载宿主机根目录、读取环境变量中的生产密钥,或者通过 /var/run/docker.sock 控制其他容器。设计时至少要明确四条边界。
文件边界
只挂载当前任务需要的仓库。输入代码可以设为只读,把补丁、构建产物和测试报告写入单独的输出目录。不要挂载 $HOME、~/.ssh、~/.aws、~/.kube 或宿主机根目录。
进程边界
容器应使用非 root 用户,删除 Linux capabilities,启用 no-new-privileges,限制进程数、内存和 CPU。这样即使代理运行 fork bomb 或恶意构建脚本,影响也更容易被容器边界吸收。
网络边界
离线分析任务可以直接禁用网络。确实需要下载依赖或调用模型 API 时,可以这样实践:通过代理服务器建立域名白名单,并把依赖下载与代码执行拆成两个阶段。单纯允许所有出站 HTTPS 并不算严格隔离。
身份边界
不要把开发者本人的云密钥传给代理。为每个任务签发短期、可审计的身份,并限定环境、资源、动作和有效期。例如,一个负责验证 Terraform 变更的任务可以读取测试环境状态,但不应拥有生产环境的 destroy 权限。
可以直接运行的最小隔离实验
下面的命令使用标准 Docker 能力演示文件、权限、网络和资源隔离。运行前只需安装 Docker;命令会创建一个临时目录,并让容器尝试写入只读输入目录。
set -eu
DEMO_DIR="$(mktemp -d)"
mkdir -p "$DEMO_DIR/repo" "$DEMO_DIR/output"
printf 'production-like-data\n' > "$DEMO_DIR/repo/important.txt"
# 写入只读仓库应该失败;输出目录仍然可以接收任务结果。
docker run --rm \
--network none \
--read-only \
--cap-drop ALL \
--security-opt no-new-privileges:true \
--pids-limit 64 \
--memory 256m \
--cpus 1 \
--user 65532:65532 \
--mount type=bind,src="$DEMO_DIR/repo",dst=/repo,readonly \
--mount type=bind,src="$DEMO_DIR/output",dst=/output \
--tmpfs /tmp:rw,noexec,nosuid,size=64m \
alpine:3.20 sh -c '
cat /repo/important.txt
if echo deleted > /repo/important.txt; then
echo "ERROR: read-only boundary failed" >&2
exit 1
fi
echo "analysis completed" > /output/result.txt
'
cat "$DEMO_DIR/repo/important.txt"
cat "$DEMO_DIR/output/result.txt"
rm -rf "$DEMO_DIR"
这个示例并不等同于完整的 Docker Sandboxes 产品配置,但它展示了同一类安全原则。实际接入编码代理时,可以把 alpine:3.20 替换为经过审核的代理镜像,并保留这些限制。需要注意,输出目录必须允许容器用户写入;生产环境中应提前设置正确的属主和权限,而不是使用 chmod 777。
对于可重复执行的任务,可以将约束固化到 Compose 文件。下面是一个需要按代理镜像和命令改造的模板:
services:
coding-agent:
image: ${AGENT_IMAGE:?set AGENT_IMAGE to an approved immutable image}
command: ["agent", "--repo", "/repo", "--output", "/output"]
user: "65532:65532"
read_only: true
network_mode: "none"
cap_drop:
- ALL
security_opt:
- no-new-privileges:true
pids_limit: 128
mem_limit: 1g
cpus: 2
volumes:
- ./repo:/repo:ro
- ./output:/output:rw
tmpfs:
- /tmp:rw,noexec,nosuid,size=128m
运行时明确指定审核过的不可变镜像标签或 digest:
mkdir -p repo output
AGENT_IMAGE='registry.example.com/coding-agent@sha256:REPLACE_WITH_REAL_DIGEST' \
docker compose run --rm coding-agent
不要把 Docker Socket 加进这个 Compose 文件。若代理必须构建容器镜像,应使用隔离的远程构建服务、专用 BuildKit 实例或一次性虚拟机,并为构建任务单独授权。
沙箱之外,还需要部署闸门
沙箱可以控制代理执行代码的范围,但无法自动判断一个补丁是否应该发布。生产变更仍需经过独立控制面。
推荐把工作流拆成几步:代理在无生产凭据的沙箱中生成补丁;CI 在独立环境运行测试和安全扫描;部署系统展示计划结果;高风险操作由人工或策略引擎批准;部署使用短期身份执行,并记录资源、提交、审批人和任务 ID。
高风险命令不能只靠字符串黑名单拦截。rm -rf 很明显,但数据库迁移、Terraform 资源替换、对象存储生命周期规则和 Kubernetes namespace 删除同样可能导致大范围损失。审批应基于最终计划和资源变化,而不是只检查代理输入的命令文本。
回滚能力也必须单独验证。备份存在不代表能够在目标时间内恢复。定期执行恢复演练,记录恢复时间目标,并确认代理身份没有删除备份、审计日志或恢复密钥的权限。
接入编码代理前的检查清单
- 代理是否运行在一次性容器、沙箱或虚拟机中?
- 是否只挂载当前仓库,且输入目录默认只读?
- 是否禁止访问 Docker Socket、宿主机根目录和开发者家目录?
- 身份是否按任务签发、短期有效,并限制到特定环境和动作?
- 网络是否默认关闭,或通过可审计的白名单代理开放?
- 生产部署和破坏性变更是否经过独立审批?
- 日志是否记录代理输入、工具调用、身份、代码提交和部署结果?
- 备份是否与执行身份隔离,并经过真实恢复演练?
13 小时的中断提醒我们:编码代理的能力越强,安全设计就越不能依赖它“做出正确判断”。把代理视为不受信任的自动化程序,用 Docker 沙箱缩小执行范围,用作用域身份限制权限,再用审批和恢复机制保护生产环境,才能让一次错误保持为可控的失败。