你批准的命令也可能不安全:用 Docker 隔离 AI 编码代理

2026-08-18 43 预计阅读时间: 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.

预计阅读时间:9 分钟

AI 编码代理通常会在执行命令前请求确认,但“需要批准”不等于“命令可信”。当代理进入一个受攻击者影响的仓库时,你批准的可能只是熟悉的 npm testmake testpytest;真正运行的代码,却藏在 package.jsonMakefile、测试夹具、编译插件或依赖安装脚本中。

问题的关键不只是代理会执行什么命令,而是这条命令能接触到什么。Docker Sandbox 一类隔离环境的价值,正是把攻击发生后的可达范围压缩到容器内部。

危险藏在命令背后

假设代理建议执行:

npm test

这条命令看起来很普通,但 npm 会根据仓库中的 package.json 解析脚本。一个经过篡改的项目可以这样定义它:

{
  "name": "demo-project",
  "private": true,
  "scripts": {
    "pretest": "node scripts/prepare-test.js",
    "test": "node --test"
  }
}

用户批准的是 npm test,实际执行链却包括仓库作者控制的 scripts/prepare-test.js。同样的问题也可能出现在以下入口:

  • make test 会解释仓库中的 Makefile
  • pytest 会导入测试模块、插件和 conftest.py
  • pip install .npm install 等操作可能触发构建或生命周期脚本。
  • 编译器、代码生成器和格式化工具可能加载项目级插件或配置。

因此,审批界面只能说明“用户同意运行这个入口命令”,不能证明命令展开后的全部行为安全。只看命令名称,会遗漏仓库内容、工具约定和依赖解析共同构成的执行链。

攻击半径取决于代理能看到什么

如果编码代理直接在开发者主机上运行,恶意代码可能接触当前用户拥有的资源,例如:

  • ~/.ssh、云平台凭据和 Git 凭据;
  • 浏览器会话、环境变量和本地配置文件;
  • 已挂载的公司代码库与共享目录;
  • 本机网络可访问的数据库、元数据服务和内部 API;
  • Docker Socket,以及它背后的宿主机控制能力。

容器不能让恶意代码变得可信,但可以改变后果。一个边界清晰的 Sandbox 应只挂载当前任务所需的工作目录,使用非 root 用户,删除 Linux capabilities,限制网络,并避免向容器传入宿主机密钥。

这里必须强调边界:把代码放进容器并不自动等于安全。以读写方式挂载整个主目录、传入云凭据,或者挂载 /var/run/docker.sock,都会让隔离效果大幅下降。容器还共享宿主机内核;面对高风险、不受信任的代码时,虚拟机或更强的沙箱运行时通常能提供更明确的隔离边界。

可以这样实践:建立一个最小权限工作区

下面是一个可直接改造的 Docker Compose 示例。它把当前仓库挂载到 /workspace,根文件系统设为只读,使用非 root 用户,并默认关闭网络。

将以下内容保存为 compose.sandbox.yaml

services:
  agent-shell:
    image: node:22-bookworm-slim
    working_dir: /workspace
    user: "1000:1000"
    command: ["sleep", "infinity"]
    network_mode: "none"
    read_only: true
    cap_drop:
      - ALL
    security_opt:
      - no-new-privileges:true
    pids_limit: 256
    mem_limit: 2g
    cpus: 2
    tmpfs:
      - /tmp:rw,noexec,nosuid,size=256m
      - /home/node:rw,nosuid,size=128m
    volumes:
      - .:/workspace:rw

运行前需要确认宿主机用户的 UID/GID。示例中的 1000:1000 适合不少 Linux 工作站,但不是通用值,可通过以下命令查看:

id -u
id -g

然后启动沙箱并在其中执行测试:

docker compose -f compose.sandbox.yaml up -d
docker compose -f compose.sandbox.yaml exec agent-shell npm test
docker compose -f compose.sandbox.yaml down

这个配置的意图很具体:即使 npm test 间接执行了仓库中的攻击代码,它默认也无法访问宿主机主目录,不能直接联网,不能写入容器系统目录,也没有额外 capabilities。

它仍然能够修改当前仓库,因为工作区是读写挂载的。对于只需要代码审查、静态分析或生成补丁预览的任务,可以进一步改为只读:

volumes:
  - .:/workspace:ro

如果工具必须写入构建目录,可以单独提供一个临时卷,而不是开放整个仓库:

volumes:
  - .:/workspace:ro
  - build-output:/workspace/dist

volumes:
  build-output:

实际项目还需要根据技术栈调整镜像与命令。例如 Python 项目可把镜像换成 python:3.13-slim,再执行 python -m pytest。这些配置是可以采用的隔离实践,并不意味着某个特定 Sandbox 产品只支持这一种实现。

网络不能只分成“开”和“关”

完全禁网适合本地测试、静态分析和已有依赖缓存的任务,但首次安装依赖通常需要访问包仓库。直接恢复全部网络访问虽然方便,也让恶意脚本获得外传数据和探测内网的通道。

更稳妥的工作流是把依赖获取与不受信任代码执行拆开:

  1. 在受控阶段生成并审核锁文件。
  2. 通过内部镜像或代理下载依赖并建立缓存。
  3. 在禁网沙箱中安装缓存内容并运行测试。
  4. 只有明确需要访问外部服务的任务,才临时开放目标域名或代理。

还应避免把秘密作为普通环境变量注入整个代理会话。环境变量对进程及其子进程通常可见,仓库中的测试代码也可能读取它们。确需访问凭据时,应使用短期、低权限、限定目标资源的令牌,并让凭据只在最短的命令阶段出现。

落地时检查这些边界

为编码代理设计执行环境时,可以逐项确认:

  • 默认把仓库视为不受信任输入,即使入口命令看起来熟悉。
  • 只挂载当前任务需要的目录,不挂载整个主目录。
  • 不把 SSH、Git、云平台凭据永久注入沙箱。
  • 不挂载 Docker Socket;它往往等价于把宿主机控制权交给容器。
  • 使用非 root 用户,并启用 no-new-privileges、capability 删除和资源上限。
  • 默认关闭网络,再按任务开放最小访问范围。
  • 将依赖下载、构建、测试和发布拆成权限不同的阶段。
  • 对工作区变更保留 diff,任务结束后销毁临时容器与卷。
  • 对高价值代码、生产凭据或强对抗场景,考虑虚拟机或专用隔离运行时。

命令审批仍然有价值,但它解决的是交互授权,不是代码可信度。真正可靠的策略,是假设一次合理的批准最终可能触发攻击者控制的代码,然后通过文件系统、网络、身份与资源边界,把这次执行能够造成的影响限制在可接受范围内。


相关推荐