一次 13 小时生产事故之后:如何用 Docker 沙箱约束 AI 编码代理

2026-07-20 29 预计阅读时间: 1 分钟
来源: docker.com AI 摘要 Original link

Disclaimer: This article is an AI-assisted summary. Read it together with the original source when precision matters. The summary may omit context, version differences, or edge cases and is not official documentation.

预计阅读时间:11 分钟

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 沙箱缩小执行范围,用作用域身份限制权限,再用审批和恢复机制保护生产环境,才能让一次错误保持为可控的失败。


相关推荐