编码代理惊悚故事:你已经批准的命令

这是“AI 编码代理惊悚故事”系列第5篇,回顾涉及 AI 编码代理的真实安全事件,并介绍 Docker Sandboxes 如何在执行边界限制代理,而非仅在命令行检查命令。

第1篇梳理了六类 AI 编码代理故障及其反复发生的原因。代理以你的身份运行,拥有你的文件系统权限与凭据,模型决策和 shell 执行之间没有阻隔。第2篇深入介绍 rm -rf ~/ 事件;第3篇将同类问题带入生产云环境;第4篇追踪供应链攻击中的凭据流向。

本篇讨论安全网。如今大多数运行编码代理的团队,都维护着某种无需询问便可运行的命令列表,背后的假设是:危险操作会出现提示,用户可以拒绝。但在1月,Pillar Security 研究人员证明这一假设并不成立。

本次事件:批准的命令执行了另一件事

2026年1月14日,Pillar Security 研究人员披露 Cursor 漏洞 CVE-2026-22708。代理在启用允许列表的自动运行模式下运行时,少数 shell 内建命令既不出现在列表中,也无需批准即可执行。README、依赖或 issue 评论等任何能将文字呈现给代理的内容,都可以利用它们悄悄修改环境变量。之后,开发者批准的普通命令,例如 git branch,实际却执行攻击者代码。Cursor 将漏洞评为高危,并在2.3版本修复。

事件没有涉及内存损坏,也没有权限提升。开发者看到准确的提示,批准本来无害的命令,却仍导致任意代码执行,因为早在一分钟前,他们从未看到的操作已经改变了命令的含义。

本文将介绍:

  • shell 内建命令如何绕过按设计运行的允许列表。
  • 为什么即使允许列表完全为空,攻击仍然有效。
  • Docker Sandboxes 限制了什么,以及未限制的两个方面。
  • Kit、组织策略与审计日志如何弥补每台笔记本上的允许列表遗漏。

image1 1

图注:漫画展示注入指令如何在不触发批准提示的情况下改变环境设置,使开发者正当批准的命令转而运行攻击者载荷。

问题所在

程序通常在启动时从环境中读取设置。Git 检查 PAGER,决定使用哪个程序显示输出;Python 检查 PYTHONWARNINGS。人们通常不会关注这些变量,这正是问题所在。修改它们的命令是 shell 内建命令,Pillar 的研究具体指出 export、typeset 和 declare,这一细节也在披露时被独立报道。内建命令并非磁盘上的程序,而检查器寻找的是磁盘上的程序,因此它们被放行,且没有呈现给用户。

于是整个攻击仅需两行:

# This one runs silently. You are never asked.
export PAGER="open -a Calculator"

# This one you are asked about, and you say yes, because obviously.
git branch

Git 查询 PAGER,决定如何显示分支列表,发现其中放着攻击者的命令,便执行了它。Pillar 指出,即使允许列表完全为空——所提供的最严格设置——这一做法仍然有效。

允许列表检查眼前的命令是否在列表中,这可以减少打断;没人愿意一上午第90次批准 ls。但命令名称不能说明命令实际会做什么。检查器读取名称并放行,而决定真正行为的设置,早在一分钟前就被检查器从未看到的操作修改了。

Cursor 文档如今将允许列表描述为尽力而为,并警告可能遭到绕过。Pillar 更进一步,主张在隔离环境内给予代理完整命令执行能力,并认为业界应该彻底弃用允许列表。

问题的规模

底层技巧并不新鲜。Pillar 的文章追溯到 Elttam 在2020年对环境变量的研究,其展示了如何利用这些设置实现代码执行。

这一技巧存在了6年,并未引发太大困扰。成功利用需要先进入某人的机器,按正确顺序设置多项内容,并亲自运行每一步;已有这种访问能力的人,通常有更快的破坏方式。

编码代理出现后,一次性移除了这些障碍。它们遵循被要求读取的文件中的指令,连续运行多个步骤而不暂停核查,并以你的身份执行。过去需要有人坐在键盘前的技巧,如今可以随早晨克隆的仓库到来。

这与第4篇中的 s1ngularity 攻击具有相同形态。那里,投毒软件包借用已经登录的代理;这里,投毒文本借用已经批准的命令。两者都没有破坏权限机制,而是将有意授予的权限用于无人预期的目的。

技术拆解:攻击如何运作

image2

图注:注入指令在不可见处改变 shell 环境,开发者批准允许列表中的命令时,该命令便携带攻击者载荷。

攻击分为两部分,这种拆分正是整套技巧的关键。

1. 你从未看到的部分

代理读取你要求它读取的文件,其中却包含写给代理、而非写给你的指令。内建命令随后悄悄设置环境,屏幕上没有任何提示。

Pillar 展示了更长的版本,将 PYTHONWARNINGS、BROWSER、PERL5OPT 等多个设置串联,使机器上之后每次 python3 命令都会运行攻击者代码。细节虽不同,原则相同:改变程序启动时读取的内容,就改变了它的行为。

2. 你批准的部分

接着,你运行 git branch 或 python3 script.py,或代理依据允许列表替你运行。这些正是人们为减少频繁打断而加入列表的命令,所以列表调得越顺手,触发器反而越可靠。载荷以你的权限运行。

一些变体完全跳过批准。例如,向 ~/.zshrc 追加内容,使每次打开终端都会再次运行代码。即使完成项目并删除仓库,下个月仍可能继续执行。

影响

Pillar 研究中的完整攻击链,最终导致受害者的 SSH 私钥离开机器。

向前追溯,一切始于文件中的一段文字,被正在按要求工作的代理读取。没有内存漏洞,没有权限提升,也没有任何日志内容看起来不合常理。

Pillar 在2025年8月报告问题,修复在次年1月发布。Cursor 积极处理报告并做出实质修改:解析器不能分类的内容现在都需要批准,封堵了已展示的路径。5个月说明在发现问题的层面修复它有多棘手,而不是对供应商的抱怨。

更广泛的问题仍在,因为它从来不只关乎 shell 内建命令,而是当检查器审查命令时,有人已经改变了周围的执行环境。

image4

图注:同一个载荷在 microVM 内执行,以及它能接触和不能接触的资源。

Docker Sandboxes 如何在执行层限制影响

Docker Sandboxes 在隔离的 microVM 中运行 AI 编码代理,每个实例有自己的内核、文件系统和默认拒绝的网络。代理拉取的受侵害依赖因此不能越过该边界接触宿主机、其凭据或其他工作负载。代理在隔离环境内部可以运行任何命令,包括 sudo,这正是 Pillar 的建议;也没有允许列表可供绕过。关于为什么共享内核不适合这种工作负载,我们在《不可信的自主工作负载》中作了更详细讨论。

现在,在沙箱中重新运行同一个攻击,看看它能走到哪里。

注入仍然成功,环境被改变,git branch 仍会触发载荷。沙箱不会阻止这些步骤。随后,载荷寻找 SSH 私钥,却找不到:主目录位于边界另一侧,沙箱内没有 ~/.ssh/id_rsa 可供复制。

它仍然可以使用密钥。Sandboxes 将 SSH agent 套接字转发到沙箱内,使 git push 等普通工作正常运行,也意味着内部代码可以请求 agent 代为认证。它不能带走密钥,但在沙箱运行期间可以借用它。限制这种使用的是网络策略:SSH 连接之前,必须有明确指定目的地址与端口的规则。

~/.zshrc 技巧在此失效,因为文件位于宿主机,而沙箱内写入的投毒副本会随着沙箱销毁而消失。

向外传出数据也比想象中困难。HTTP 与 HTTPS 只能经过宿主机上的代理,它逐个请求检查规则;其他 TCP 连接需要明确地址与端口的规则;UDP 与 ICMP 则直接阻止。

Docker 安全文档明确指出两个限制。首先是工作区,默认会与宿主机实时共享,因此 Git hook 和 Makefile 目标仍可被修改,而且投毒 hook 不会出现在 git diff 中。使用 –clone 可为代理提供独立副本。

其次是共享的代理技能存储。受支持的代理默认以读写方式挂载相同的宿主机侧存储,除非创建时选择退出;这使代理可以改进并持久保存技能。共享该存储的沙箱处于同一信任边界,一个沙箱修改的技能会成为下一个加载它的沙箱的输入。不过,该存储是沙箱状态,修改后的技能本身不会在宿主机执行,因此风险是沙箱到沙箱,而非沙箱到宿主机。

隔离也存在接缝。7月,Pillar 发布了涉及四种编码代理的一系列沙箱逃逸研究,其机制并非沙箱损坏,而是沙箱内写入的文件随后被沙箱外工具信任。上述两个限制都具有同样的形态。

这些措施不会阻止注入,而是改变注入能够触及的范围。这才是此问题中能获得可靠答案的部分。

用 Kit 将边界写成配置

image3

图注:Kit 声明代理的工具、文件与网络规则;真实凭据留在宿主机,由出站转发代理注入。

允许列表失败的部分原因在于:它在每台笔记本上分别编辑,而注入可以绕过它。Kit 是 Docker 对“每台笔记本分别编辑”问题的解决方案。

Kit 是声明式 YAML 产物,用来为沙箱代理扩展凭据、网络策略、环境变量、启动命令与文件。开发者不再各自维护个人允许列表,而是只编写一次边界:默认拒绝网络,只允许任务确实需要的目的地,然后向所有人分发相同 Kit。它像仓库内其他文件一样审阅、版本管理与比较差异。字段见 Kit 规范参考,可运行示例见 docker/sbx-kits-contrib。

这直接对应前面的 SSH 问题。转发的 SSH agent 是仅受网络策略约束的有效凭据。如果策略依赖谁还记得执行 sbx policy deny,就仍是本篇讨论的逐台设备管理弱点。Kit 可以预置网络规则,使不可信工作没有 SSH 出站权限,除非目的地预先声明。

实际操作

漏洞位于编辑器,所以需要将编辑器终端置于隔离环境。Cursor 基于 VS Code,以相同方式通过 Remote – SSH 连接;编辑器留在本机,而文件、终端和扩展在沙箱运行。需要 Docker Sandboxes 0.37.0 或更新版本、已配置的 SSH 访问,以及 Cursor 的 Remote – SSH 支持。完整步骤见 Cursor 集成指南。

# One-time setup: configure your SSH client for sandboxes.
sbx setup ssh
# Check the sandbox is reachable, then open the Command Palette,
# run Remote-SSH: Connect to Host, and enter <name>.sbx
ssh demo.sbx
# See what this sandbox is currently allowed to reach.
sbx policy ls
# Shut egress down and open only what the task needs.
sbx policy deny network "**"
sbx policy allow network "github.com,registry.npmjs.org"

最后两条命令有一个条件:如果组织启用治理,组织策略会替换本地策略,sbx policy allow 与 sbx policy deny 在本机就不会生效。此时 sbx policy ls 输出开头会显示 Governance: Managed by <org>。管理员可能将某些规则类型委托回本地控制,但本地允许绝不能覆盖组织级拒绝。

编辑器、代理、允许列表、载荷都相同;变化的只是终端所在的机器。

情况 笔记本本机 沙箱内部
载荷执行 是 是
执行位置 你的机器,以你的身份 具有独立内核的 microVM
SSH 密钥文件 可读取并复制 不存在
SSH 认证 可用,包含密钥 可用,但密钥留在外部
~/.zshrc 技巧 长期持久存在 随沙箱销毁
向外发送数据 默认开放 仅策略允许的位置
规则制定者 每位开发者 组织
事后证据 无 记录的策略决策

让整个团队持续执行边界

Kit 把某个开发者头脑中的边界转化为团队共享文件,但关键机器上的文件仍可被忽略或修改。Docker AI Governance 将设置再提升一个层级:管理员统一定义网络与文件系统规则,经开发者现有登录下发,无需逐机配置,也不会有人悄悄重新打开安全团队关闭的权限。共享 Kit 是建议性的边界,治理则是不可超越的上限。

对本事件最重要的是记录。CVE-2026-22708 的第一阶段不可见,没有提示,也没有记录在你会查找的日志中。治理模式下,每次策略决策都生成包含用户、时间戳和触发规则的事件,并流入安全团队现有的 SIEM。

这样,即使攻击在沙箱内成功,随后试图访问不该访问的地方,也会留下线索。这比检查器发现“没问题”却不告诉任何人有用得多。

最佳实践

1. 像对待其他命令一样对待 export。任何改变环境设置的操作,都可能改变下一条命令的行为,即使该命令位于允许列表中。

2. 不要把允许列表当成安全边界。它减少打断,而供应商文档已明确说明,它只是尽力而为,并非安全保证。

3. 在执行第一条命令前隔离,而不是等出现异常。不可信包括任何不是你编写、也尚未阅读的内容,依赖树中的大部分内容都在此列。

4. 对未经审查的代码使用 –clone,并退出共享技能存储。否则 Git hook 和构建脚本仍实时作用于宿主机,投毒 hook 不会出现在 git diff 中;一个沙箱修改的技能,也会等待下一个沙箱加载。

5. 记住,转发的 SSH agent 是有效凭据。密钥文件留在本机,并不意味着密钥无法被使用,因此应限制不可信工作的出站网络。

6. 阅读自己的策略,运行 sbx policy ls。默认拒绝却配置冗长允许列表,可能比想象中更接近默认允许。

开始行动

  • 安装 Docker Sandboxes:访问文档,安装 sbx,并在 microVM 中运行第一个代理。
  • 连接编辑器:Remote – SSH 集成将终端放在边界内,编辑器留在本机,工作流程基本不变。
  • 用 Kit 声明边界:一次性定义团队所需的网络与凭据规则,分发相同产物,取代各自的允许列表。
  • 阅读安全模型:文档明确说明隔离和未隔离的内容,包括 –clone 与退出共享技能存储选项所改变的默认行为。
  • 启用审计日志:Docker AI Governance将策略决策发送到 SIEM,使静默绕过成为可调查事件。

结语

CVE-2026-22708 令人不安之处,在于故事中的参与者都没有明显做错。

Cursor 构建了大家要求的允许列表,开发者批准了任何人都可能批准的 git branch,检查器完成了审查命令并确认可接受的职责,攻击却仍然成功。

让代理正确判断读取到的每条指令,随着能力增强而愈加困难,也没有简单的终点。限制代理能触及什么,则是早已解决的问题。更有价值的方向,是减少对前一个问题的依赖,将可访问范围写成可审阅的产物,而不是各台笔记本分别保存的列表。

系列第6篇将介绍 ClawHub 信息窃取活动:恶意技能通过市场排名漏洞到达开发者机器,并讨论沙箱化技能执行和共享技能存储,如何改变无法逐一审查注册表时的风险。

进一步阅读

来源与版权

原文:Coding Agent Horror Stories: The Command You Already Approved。作者:Ajeet Singh Raina(Docker Developer Advocate)。原文日期:2026-08-18。

© 2026 Docker Inc. All rights reserved.

Ajeet Singh Raina 是 Docker 的开发者布道师,撰写和分享容器、Docker Compose 与 AI 相关内容,帮助开发者更有把握地构建软件。

© 版权声明
THE END
喜欢就支持一下吧
点赞0 分享
评论 抢沙发

请登录后发表评论

    暂无评论内容