返回所有文章
发布于 · 作者 Renaud Deraison

那个走出工作区的符号链接,什么也没捞到

GhostApproval 展示了一个恶意仓库如何诱使六款 AI 编码助手在它们本应停留的文件夹之外读写文件:植入 SSH 密钥、劫持 shell 启动、读取开发者自己机器上的云凭证。漏洞本身是一个跟随符号链接的失误;它之所以伤人,是因为代理跑在一台装满你机密的机器上。Bromure Agentic Coding 改变的是后一个事实。

编码代理是一个你请上笔记本、允许它读写文件的程序。GhostApproval 讲的是一个恶意仓库说服这个程序稍稍越界书写的故事:写进你的 SSH 密钥、你的 shell 配置、你的云凭证。漏洞本身既老且钝,伤害来自 代理站立的位置。

2026 年 7 月 8 日,Wiz 的研究团队发布了 GhostApproval, 记述了同一个缺陷如何以六种不同的形态,出现在六款流行的 AI 编码助手 中:Amazon Q Developer、Anthropic 的 Claude Code、Augment、Cursor、 Google 的 Antigravity,以及 Windsurf。细节因产品而异,形状却如出 一辙。你打开一个仓库——也许是别人转来的教程,也许是同事请你看一眼 的项目——把你的编码代理指向它。而在那个仓库的某个角落,藏着一个 不是文件的文件。

一个不是文件的文件。

把戏在于符号链接(symlink):一种文件系统条目,看上去像文件, 实则是指向另一条路径的路标。符号链接是一项寻常的、有四十年历史的 Unix 特性。它的安全问题——编目为 CWE-61——几乎 同样古老:程序若不检查符号链接指向何处就把它打开,攻击者便能把它 引向作者从未想让它触碰的文件。

在 GhostApproval 中,恶意仓库带着一个名字人畜无害的符号链接,比如 project_settings.json。它装的不是设置,而是一枚指针,指向 ~/.ssh/authorized_keys~/.zshrc~/.aws/credentials—— 它们坐落在开发者的主目录里,远在仓库文件夹之外。随后,一份 README(或是按仓库指示行事的用户)让代理“更新 project_settings.json”。代理不解析路标通向哪里就跟着它走,把 内容写进了真正的目标。

工作区——克隆的仓库project_settings.json看似配置文件,实为符号链接“请更新 project_settings.json”——出自仓库 README,代理照办工作区边界被当作安全——这个假设正是漏洞开发者主目录 (~)——仓库之外~/.ssh/authorized_keys~/.zshrc~/.aws/credentials真实主机访问 · 真实云密钥 · 每次登录都运行的 shell代理跟随链接CWE-451——提示歪曲了即将发生的事情代理的真实目标:~/.ssh/authorized_keys展示给你的:“编辑 project_settings.json?[是]”
GhostApproval 的原理。克隆下来的仓库里藏着一个伪装成配置文件的符号链接,它指向工作区之外、开发者的主目录。代理编辑它时,写入落在 ~/.ssh/authorized_keys、~/.zshrc 或 ~/.aws/credentials 上。更糟的是,在多款产品中,确认提示展示的是代理被要求编辑的那个无害名字,而不是它即将触碰的敏感路径(CWE-451)。

有两件事让它不止于猎奇。第一是这些目标各自的分量。往 ~/.ssh/authorized_keys 追加一行,就为攻击者装上了免密 SSH 登录 你机器的密钥;写入 ~/.zshrc,你每次打开终端都会运行攻击者的 命令。Augment 的代理甚至不需要写:被问到“项目里的 AWS 密钥”时, 它顺着符号链接走了出去,找到一份凭证文件,把里面的机密直接打印进 聊天,全程没有任何提示。

第二是一处更隐蔽的失守,Wiz 将其归为 CWE-451,即用户 界面失实。在 Claude Code 中,代理自己的内部推理已经认出那是一份 shell 配置文件,确认对话框却仍然只问“要对 project_settings.json 做此修改吗?”人类本可拦下危险的那一个 瞬间,展示给人类的却是错误的信息。Windsurf 和 Amazon Q Developer 更糟:它们在展示批准按钮之前就把文件写上了磁盘,于是对话框成了 撤销提示,而非闸门。攻击者的 SSH 密钥早已就位。

多数厂商此后修复了各自的变体:Cursor 在 v3.0 (CVE-2026-50549), Amazon Q 在 language-server 1.69.0 (CVE-2026-12958), Google 在 Antigravity v1.19.6,Claude Code 则加上了符号链接解析 警告。这是正确的回应,你也应当安装这些更新。但逐一修补每款产品的 符号链接处理,修好的只是机制赌注还留在原地。

赌注由代理站在哪里决定。

这些载荷无一例外建立在同一个假设上:够到开发者的主目录是值得的。 ~/.ssh 里有一把能打开真实机器的密钥,~/.aws/credentials 里有 一枚活的云令牌,~/.zshrc 会在你明天打开的 shell 里运行。符号链接 是路,终点则是一台装着你全部工作生涯的笔记本。

大多数针对编码代理的攻击都倚仗同一个假设。代理是一个能力强大、 行为开放的程序,它读取不受信任的输入(仓库、网页、软件包压缩包、 工具输出),并在一台机器上采取行动——而你的凭证就躺在几个目录 之外的明文文件里。任你如何加固代理,它仍与你运行的其他一切共享 同一个文件系统、同一串钥匙。攻击者会不断铺出新的路。问题在终点。

Bromure Agentic Coding 拿掉的正是这个终点。代理、它派生的 shell、 它克隆的仓库,以及其中的任何代码,都运行在一台按配置档划分的 Linux VM 里:一台由 hypervisor 与你的 Mac 隔开的独立机器。你的 真实凭证从不进入其中。我们在为什么 Bromure Agentic Coding 不是沙箱里写过为什么 边界胜过牢笼;GhostApproval 是对这个想法的一次干净检验,因为它 逃出的正是工作区文件夹——业界所倚赖的那道小小边界。

代理在你的 Mac 上一台机器,一个主目录代理 + 克隆的仓库符号链接 → ~/.ssh/authorized_keys~/.ssh/authorized_keys真实~/.aws/credentials真实~/.zshrc真实结果攻击者的 SSH 密钥已装入,云密钥被读走,shell 被劫持——就在你日常工作的机器上。Bromure Agentic Coding每配置档一台 VM——一次性代理 + 克隆的仓库符号链接 → ~/.ssh/authorized_keys写入仍会发生~/.ssh/authorized_keys桩值~/.aws/credentials桩值~/.zshrc客户机结果密钥装进一台随手可弃的客户机,被偷走的只是占位云密钥——你的 Mac 从未进过这个房间。
同一次符号链接逃逸,两台机器。在普通配置下,代理与你真实的 ~/.ssh 和 ~/.aws 比邻而居,顺着链接就能摸到活的密钥和一台值得攻陷的机器。在 Bromure 中,代理运行在按配置档划分的 VM 里;符号链接照样解析,写入照样发生,却落在一台一次性客户机上——它的主目录里只有桩值:在公共互联网上毫无意义的占位文件。你 Mac 的文件系统位于 hypervisor 这条线之下,从不挂载进 VM。

把同一次逃逸放到 VM 里走一遍。符号链接照样解析(Bromure 根本不 需要识破或拦截这个把戏),写入照样落在 ~/.ssh/authorized_keys 上。但那个文件住在客户机的主目录里,在一台没有任何通路通向你 Mac 的一次性机器上。攻击者的密钥如今换来的,是登录一台你随时会 扔掉的机器的权限——而这台机器从一开始就无法从外部访问。

凭证窃取那个变体的结局更加索然。当一个被攻陷的代理顺着符号链接 走到 ~/.aws/credentials,把内容打印进聊天时,它读到的是一份 桩值:格式规整、密钥却在任何地方都无法通过认证的凭证文件。 真实令牌留在宿主上,由一个中介看管——只在你允许的出站调用发出的 那一刻,它才把占位符换成真机密,并在响应里再换回去。翻遍文件系统 找机密的依赖包或被骗的代理,找到的只是摆设。

确认提示,与第二张网。

对 GhostApproval 两处更锋利的刃口,Bromure 在不同的层上接招。

第一处是确认提示的问题,CWE-451:那个代理心里想着 ~/.zshrc、 嘴上却说 project_settings.json 的对话框。Bromure 干脆取消了 文件写入对话框;一次性 VM 内的写入只触及一块可以随时报废的磁盘, 无需批准。Bromure 把人插在真正的边界上:那些离开 VM、会改变状态 的调用——一次 git push、一条数据库 DROP、一个云端 Terminate。这些调用在宿主上暂停,向你展示字面意义上的操作本身, 而不是代理写的摘要。这正是 GhostApproval 违背的那条原则,用在了 要紧的地方:决定在沙箱之外产生后果的那一刻,就是人类应该看到 真实调用的那一刻。

第二处刃口是启动整场攻击的那条指令:一份 README 让代理“更新 project_settings.json”,模型把它当上下文来读,当命令来服从。 Bromure 会先在设备端读一遍这些不受信任的内容:一个本地分类器对 传入的文件、网页抓取和工具输出打分,找出那些本不该出现在那里的 指令;而代理奉为常设命令的规则文件,会被更严格地过一遍。分类器 会漏掉某些措辞,况且隔离本身已足以击败这次攻击;把它当作第一张网 下面的第二张。

这条线救不了你的地方。

隔离是一个具体的形状,而形状有它的边。

配置档长期存在,持久化因而持久。

~/.zshrc 里的载荷会在那个配置档的 VM 内下一次打开 shell 时运行。它面对的是一台没有宿主密钥、只有桩值凭证的客户机—— 一个空房间里的 shell——但它确实会运行。若某项任务让你为此 担心,就换一个全新的配置档来跑。

你挂载什么,就暴露什么。

VM 默认不装你的任何东西。但如果你主动把一个真实的宿主目录 挂载进某个配置档,或把一枚活令牌粘贴进客户机,符号链接逃逸 就够得到它。这条线把你的机密挡在外面;把它们放回去,是只有 你才能做的选择。

有意识地限定中介的范围。

桩值意味着磁盘上的文件一文不值,但中介仍为你允许的调用握着 真实令牌。一个只需要读仓库的配置档,就不该携带一枚能发布 仓库的令牌。隔离限住了爆炸;作用域决定它最大能有多大。

工作区是一堵错误的墙。

说到底,GhostApproval 讲的是业界共同默认了什么可以充当边界: 工作区文件夹,以及“代理待在项目目录里”这一想法——它被当成了 安全属性,实则只是任何一块路标都能绕开的默认行为。符号链接本身 是厂商能修、也确实修了的漏洞。把边界画在文件夹上,你就得永远 证明没有任何符号链接、任何 ..、任何取巧的路径能走出去。把边界 画在 hypervisor 上,这个问题就不再要紧,因为代理逃的地方, 是一台从未装过你任何东西的一次性机器。

装上厂商的补丁;它们堵上的是一个真实的洞。但这个洞之所以伤人, 是因为代理站在你的笔记本上,离你的密钥只有一个目录之遥。把代理 从你的笔记本上挪走,下一次逃逸(一定会有下一次)到达的将是一个 空房间。Bromure Agentic Coding 免费、开源, 今天即可使用。