把密钥关在编码代理够不到的地方:用 Docker Sandbox 降低供应链风险

2026-07-28 35 预计阅读时间: 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 编码代理不只是生成代码。它通常还能执行 Shell、安装依赖、读取项目文件,并调用测试或部署工具。这种能力提高了开发效率,也扩大了攻击面:一旦代理下载到恶意依赖、执行受污染的安装脚本,或者被仓库中的提示注入误导,当前进程能够读取的凭据就可能被复制或外传。

真正的问题不是代理是否“值得信任”,而是它运行时拥有多少权限。更稳妥的做法是把代理视为可能执行不可信代码的自动化进程,并通过 Docker Sandbox 把主机密钥、个人配置和高权限接口放到容器边界之外。

为什么一次依赖安装就可能泄露密钥

供应链攻击不需要直接攻破编码代理。恶意代码可以藏在包安装脚本、测试工具、构建插件或仓库脚本中。代理为了完成任务,可能主动运行:

npm install
npm test
pip install -r requirements.txt
make build

这些命令继承代理进程能够访问的环境变量和文件。如果代理直接运行在开发者主机上,风险对象可能包括:

  • AWS_ACCESS_KEY_IDGITHUB_TOKEN 等环境变量;
  • ~/.ssh~/.aws~/.config 中的凭据;
  • 云平台 CLI 的登录缓存;
  • Docker Socket,也就是接近主机 root 权限的控制接口;
  • Kubernetes 配置和生产环境上下文。

即使代理没有主动查看密钥,它启动的子进程通常也会继承环境变量。恶意安装脚本只需读取进程环境,并通过网络请求发送出去。因此,“不要让代理输出密钥”并不是完整防线;关键是让密钥从一开始就不在代理的可达范围内。

Sandbox 应当切断哪些通道

一个有效的代理沙箱至少要约束四类能力。

文件系统:只挂载当前工作区,不挂载用户主目录、SSH 目录、云平台配置或宿主机根目录。

环境变量:使用明确的允许列表,不要把宿主机环境整体转交给容器。特别要检查 .env 文件和 Compose 的自动加载行为。

网络:在不需要下载依赖时关闭网络;确实需要联网时,使用代理、域名允许列表或独立的依赖准备阶段。

宿主机控制接口:不要挂载 /var/run/docker.sock。容器内的 root 并不等于宿主机 root,但 Docker Socket 往往能让容器创建高权限挂载,从而绕过原有隔离。

一个可改造的最小 Docker Sandbox

下面的示例假设项目位于当前目录,编码代理可以通过 agent-cli 启动。运行前需要把镜像中的代理安装命令和入口命令替换成实际工具。

创建 Dockerfile.agent

FROM node:22-bookworm-slim

RUN useradd --create-home --uid 10001 agent
WORKDIR /workspace

# 按实际产品替换;固定版本,避免每次构建获得不同代码。
RUN npm install --global agent-cli@1.2.3

USER agent
ENTRYPOINT ["agent-cli"]

构建镜像:

docker build --pull -f Dockerfile.agent -t local/coding-agent:1.2.3 .

在 Linux 主机上,可以使用下面的命令启动受限容器:

docker run --rm -it \
  --name coding-agent-sandbox \
  --network none \
  --cap-drop ALL \
  --security-opt no-new-privileges \
  --pids-limit 256 \
  --memory 4g \
  --cpus 2 \
  --tmpfs /tmp:rw,noexec,nosuid,size=512m \
  --mount type=bind,src="$PWD",dst=/workspace,rw \
  --workdir /workspace \
  --env HOME=/home/agent \
  local/coding-agent:1.2.3

这个命令没有传入宿主机环境变量,没有挂载用户主目录,也没有暴露 Docker Socket。--network none 进一步阻止受污染脚本向外发送数据。工作区仍然是可写的,因此应在独立 Git 分支或临时副本中运行,并在合并前审查差异。

可以先验证隔离是否符合预期:

docker run --rm \
  --network none \
  --cap-drop ALL \
  --security-opt no-new-privileges \
  --mount type=bind,src="$PWD",dst=/workspace,rw \
  --env HOME=/home/agent \
  local/coding-agent:1.2.3 \
  sh -lc 'env | sort; test ! -e /var/run/docker.sock; test ! -e "$HOME/.ssh"'

如果代理镜像的入口点不允许执行 sh,可在验证命令中增加 --entrypoint sh

联网安装依赖时,分成两个信任阶段

完全关闭网络并不总是现实。代理可能需要安装包或查询文档。可以把工作流拆成两个阶段:由受控流程准备依赖,再让代理在离线沙箱中修改和测试代码。

以 Node.js 为例,可以先由人工检查锁文件,然后在独立容器中执行确定性安装:

docker run --rm \
  --mount type=bind,src="$PWD",dst=/workspace,rw \
  --workdir /workspace \
  --env npm_config_ignore_scripts=true \
  node:22-bookworm-slim \
  npm ci

npm_config_ignore_scripts=true 会跳过依赖安装脚本,可以降低风险,但某些原生模块可能因此无法完成安装。不要把它理解为万能开关:包本身仍会在测试或运行阶段执行。更严格的环境还应使用内部制品仓库、锁定依赖哈希、扫描新增包,并限制出口网络。

如果任务确实需要某项凭据,应创建短期、最小权限、可撤销的专用令牌,只注入单次容器:

read -rsp 'Temporary token: ' AGENT_TOKEN
printf '\n'

docker run --rm -it \
  --network agent-egress \
  --cap-drop ALL \
  --security-opt no-new-privileges \
  --env AGENT_TOKEN="$AGENT_TOKEN" \
  --mount type=bind,src="$PWD",dst=/workspace,rw \
  local/coding-agent:1.2.3

unset AGENT_TOKEN

这种方式仍然意味着容器内代码可以读取令牌。它只能缩小令牌的权限和有效期,不能让令牌对恶意进程不可见。高价值密钥更适合保留在容器外,由受控代理服务代为执行少量、经过授权的操作。

上线前的检查清单

采用编码代理时,可以从以下边界开始:

  • 默认不继承宿主机环境变量,只允许显式注入;
  • 只挂载任务所需目录,绝不挂载整个主目录;
  • 不暴露 Docker Socket、Kubernetes 配置和生产云凭据;
  • 默认关闭网络,需要联网时限制目标和时间窗口;
  • 固定代理镜像与依赖版本,并审查锁文件变化;
  • 使用非 root 用户、删除 Linux capabilities,并启用 no-new-privileges
  • 在临时分支或一次性工作区运行,完成后审查 git diff
  • 对必须使用的令牌设置最小权限、短有效期和独立审计记录。

Docker Sandbox 不是绝对安全边界,尤其不能替代及时修补的容器运行时、依赖审查和凭据轮换。但它能落实一个重要原则:编码代理可以拥有完成任务所需的代码和工具,却不应顺便拥有开发者的全部数字身份。


相关推荐