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

守卫与 shell 各说各话

6 月 30 日,Adversa AI 披露了 GuardFall——一类绕过手法,让数十年前的 bash 小把戏,直接从 11 款最热门开源编程代理中 10 款内置的命令守卫面前走过去。原因很简单,而且在那一层无法修补:守卫读的是文本,而 bash 在执行前把文本改写了。Bromure Agentic Coding 从未赌守卫能正确读懂 bash。它赌的是——命令在执行的那一刻并不重要。

某个编程代理的安全过滤器看着字符串 r''m -rf ~,看到一个它不认得的命令, 于是放行了。bash 看着同一个字符串,丢掉空引号,执行了 rm -rf ~。过滤器与 shell 读的是两种不同的语言。这道裂缝不是某个工具的缺陷——它是“用模式匹配代理 所输入的命令来守护代理”这整个念头本身,而 Adversa AI 刚刚证明,它在受测的 十一款代理中有十款失守。

命令守卫是每个代理式编程工具最先伸手去拿的功能。代理提出一个 shell 命令;过滤器 检查该字符串;若看来具破坏性——rm -rf、以管道接的 curl … | sh、对裸设备的 dd——守卫就拦下它或请你确认。它正是让你敢打开自动执行、觉得安全的那个东西。 2026 年 6 月 30 日,Adversa AI 发表了研究,The Hacker NewsSecurityWeek 都跟进报道,指出这项功能只是演戏。

GuardFall 是什么

他们称它为 GuardFall,而它不是一个 CVE。没有东西可以修补,因为它不是某个组件里 的瑕疵——而是其中两个组件之间的错配。用 Adversa 的话说:“守卫检查原始文本,而 系统 shell(bash)在执行前会展开、去引号并改写文本。”过滤器匹配的是模型吐出的 字符串。bash 接着让那个字符串经过去引号、变量展开与命令替换,才会有任何一个字节 被执行。两者从不看同一个命令,因此任何能撑过改写、却撑不过匹配的把戏,都能一路 畅行。

这些把戏比大多数使用这些代理的人还老。Adversa 把它们归成五类:

  • 去引号。 r''m 对正则表达式是两个记号,对 bash 却是单词 rm,因为它丢掉 了空引号。
  • 变量展开。 rm$IFS-rf$IFS/ 对模式匹配器看来像一个长词;bash 展开内部字段 分隔符,得到 rm -rf /
  • 命令替换。 $(echo rm) -rf / 把危险的动词藏在 $( ) 里;检查外层字符串 的守卫从未看见 rm
  • 编码管道。 echo <base64> | base64 -d | sh——每个可见的记号都无害;载荷 只在解码后才存在。
  • 替代的破坏性标志。 完全没有 rmfind /x -deletetar -C / -xsed -i。一份围绕 rmdd 建立的黑名单永远不会触发。

Adversa 拿这些把戏对付十一款最常用的开源编程与电脑操作代理——其中包括 opencode、 Goose、Cline、Roo-Code、Aider、Plandex、Open Interpreter、OpenHands、SWE-agent 与 Hermes 项目,到五月时合计约 548,000 颗 GitHub 星。 十款倒下。唯一撑住的 Continue,撑住有个明确的理由:它不匹配原始字符串,而是在 决定之前,照 bash 将要做的方式把命令记号化——吸收引号、解析 $IFS、从替换中 取出内层命令、对任何以 sh 结尾的管道做标记。它为 shell 建模。那是守卫这一层 唯一有效的做法,是真实而细心的工程,如今其余十款都得正确地重现它,并在 bash 的 每一个边界情况与每一次发布中保持正确。

代理吐出一个字符串r''m -rf ~守卫 · 匹配原始文本看到这些记号r''m · -rf · ~没有 rm → 放行 ✓BASH · 改写后执行丢掉空引号rm -rf ~家目录没了
守卫为何会输。代理吐出一个字符串;模式匹配的守卫照字面读它,找不到任何破坏性的东西;bash 接着把同一个字符串展开、去引号、改写成 rm -rf,那时过滤器的裁决才有意义。守卫与 shell 看的是两个不同的命令——正是 GuardFall 的整个前提。

问题出在那一层,不是正则表达式

诱惑在于把 GuardFall 读成“十支团队写了弱过滤器”。那错过了 Adversa 想说的。他们 最锋利的一句,并非针对任何一款代理:

一个能在操作者主机上执行任意 shell 命令、仅靠一条匹配 LLM 所吐字符串的正则 表达式来把关的代理,并不是防御。它在完全启用且正确配置的状态下失守,因为字符串 匹配无法为 bash 将执行什么建模。

完全启用且正确配置。 这不是你能关掉的错误配置。守卫坐在它永远赢不了的那唯一 一层——在一个写字符串的模型与一个重新诠释字符串的 shell 之间——并要求一个文本 匹配器去预测一个图灵完备展开器的输出。Continue 的“记号化并规范化”做法,借由教 守卫像 bash 那样思考来缩小裂缝,而如果你打算在那里留一个守卫,这是正确的一步。 但那是一项逐代理、逐版本的承诺,要在解析上胜过一个花了三十年不断累积改写命令手法 的 shell。另外九款热门代理,正说明了要弄错有多容易。

Adversa 的补偿性控制很说明问题。在任何结构性修补之前,他们要你$HOME 导向 一个受限沙箱,保留项目访问但移除凭据,剥掉自动执行标志,别让代理在不可信的拉取 请求上执行,并把每个仓库配置文件都当成不可信的代码。再读一遍那份清单。那不是 关于怎么写一个更好的过滤器的建议。那是关于怎么在代理周围造一个箱子的建议——好让 过滤器输的时候(他们正告诉你它会输),漏过去的命令落在一个伤不了你的地方。

那个箱子就是产品。

Bromure 从未要求守卫去读 bash

Bromure Agentic Coding 从 GuardFall 所证明的假设出发:代理迟早会执行一个你未曾 核准的命令。它不试图靠读它来拦下那个命令。它把整个代理——Aider、Goose、opencode, 十款里你喜欢哪个都行——放进一台可抛弃的 Linux VM 里执行,并把每一个网络请求都经由 主机上的代理服务器送出去。设计上的问题从不是“过滤器会认出这个 rm 吗?”而是 “当那个 rm 执行时,它能碰到什么?”

把 GuardFall 自己的载荷放进那个箱子走一遍。

这个攻击的重点,一旦一个藏起来的 r''m 或一个 find /x -delete 让一份设了 陷阱的 README 溜过守卫,就是窃取这个账号能碰到的东西——Adversa 点名 ~/.ssh~/.aws、云端凭据、“任何躺在你家目录里的东西”——并把它抹除或外泄。在 Bromure 里,那些是本就不在箱子里的东西。真正的密钥从不进入 VM:当你给一个配置文件一个 AWS_SECRET_ACCESS_KEY、一个 GitHub 令牌、一个 Anthropic 密钥时,代理的环境拿到 的是一个 brm_… 占位符,而主机代理服务器只在送往应接收它的供应商的对外请求上, 于线路上把假的换成真值。Adversa 建议的缓解——把 $HOME 搬到某个“保留项目访问但 移除凭据”的地方——是 Bromure 配置文件默认就会做的事的手工、局部版本,对每一项 凭据,都不必你去写脚本。

没有 — 真家目录、真密钥GuardFall 载荷执行cat ~/.aws/credentials wJalrXUtn…cat ~/.ssh/id_ed25519 -----BEGIN…攻击者拿到可用密钥真账号、真灾情外泄有 BROMURE — 可抛弃箱、诱饵同一载荷执行(VM 内)cat ~/.aws/credentials brm_d4e5f6…cat ~/.ssh/id_ed25519 brm_a1b2c3…主机代理 — 真密钥从不进入 VM;仅在真正供应商请求时换入攻击者拿到 brm_… 诱饵什么都认证不了家目录是可抛弃克隆外泄已记 · 重置即抹去
GuardFall 的载荷在两个世界里都能通过——Bromure 并不阻止命令执行。左:在一般主机上,被注入的命令读取真正的 ~/.ssh 与 ~/.aws 内容并送出;它成功了。右:在 Bromure 里同一个命令照样执行,但家目录是一份可抛弃的克隆,它找到的凭据是 brm_… 诱饵,真正的密钥留在主机上,而外泄的尝试是安全日志里被记下的一行。命令执行了,却什么也碰不到。

破坏性的那一半也一样落地。一个溜过守卫的 find /x -deletesed -i 确实会 执行——Bromure 不假装它拦下了命令。它对着一个从共享基底克隆而来的家目录执行,在 一台你能从菜单(而非事故表单)抹除并重置回基底的 VM 上。载荷可能植入的持久性,死 在那次重置。而且因为每个请求都经由主机代理离开,GuardFall 那类编码管道本要藏起 的 curl … | base64 -d 信标并非隐形:它是安全日志里一行被记下、可归属的记录。

线路胜过 shell 的那唯一之处

Bromure 确实保有一个破坏性操作的守卫——它主机端的 Guardrails,我们曾在一个 Cursor 代理于九秒内删掉一个生产数据库 后写过它。而在这里,那一层对 Bromure 有利,因为 Guardrails 并不住在 GuardFall 住的地方。

GuardFall 是一种 shell 文本 攻击。它的五类——去引号、$IFS$( )、base64 管道、稀奇的标志——每一类都是 bash 如何解析一个字符串的把戏。Bromure 的 Guardrails 从不读那个字符串。它们坐在主机代理服务器上,对代理向它所理解的供应商 发出的结构化 API 调用进行分类——对 AWS、Kubernetes、git 平台、托管数据库的 实际 HTTPS 请求——并对破坏性的那些在线路上回一个硬性的 403,就在 VM 里被入侵的 代理关不掉它的地方。等到一个请求抵达那一层,它已经是一个被解析好的 DeleteDBInstance,而不是一串引号与美元符号。没有 shell 改写可以夹带任何东西, 因为路径上没有 shell。GuardFall 的整套技法都需要在检查与行动之间有一个 bash;在 Guardrails 的路径上,没有。

这划出了哪条界线,把话说清楚

命令照样执行

Bromure 是隔离,不是拦截。一个溜过你代理自身守卫的 GuardFall 载荷,会在 VM 里执行。Bromure 改变的是灾情范围:一个可抛弃的家目录、诱饵凭据、一条被记录的 出口路径。如果你需要命令本身被拒绝,那是你代理的守卫的工作——而 GuardFall 正是你不该倚赖它的理由。

本地破坏靠的是可抛弃性,不是阻挡

对 VM 内文件的 find /x -delete 不是供应商 API 调用,所以 Guardrails 不去 把关它。对 VM 内破坏的答案,是 VM 可抛弃、而真正的工作住在一个挂载的仓库与 主机上,不是那个删除被挡下。是重置回基底,不是回滚。

替换涵盖你所配置的凭据

brm_… 的替换保护你放进配置文件的密钥——模型密钥、云端与 git 令牌、托管数据库 端点。一个你亲手贴进箱内某文件的密码,或一个脚本在会话中途写到磁盘的令牌,就 只是一个文件。把密钥放在代理中介里,别放在工作区里。

出口被记录,并非默认禁止

外泄信标会出现在安全日志里;那是归属,不是防止。没有任何真的东西离开,因为 凭据是诱饵,但如果你需要禁止 VM 与任意主机通信,那是一条你仍要自行设定的网络 策略。

可以推而广之的部分

GuardFall 是对一条整个类别一再重新学到的规则的清晰陈述:你无法靠要求一个过滤器 去预测它的命令将做什么,来让一个代理变安全。模型写下一个字符串,而在它底下某处, 一个 shell、一个 API 客户端、一个包管理器,或一个浏览器,用它自己的规则重新诠释 那个字符串。每一种检查代理输出、并指望它与最终行为相符的防御,都是在对那道裂缝 下反向注,而裂缝总是赢——它在这里赢了,在用 Adversa 的话说完全启用且正确配置的 十一款代理中的十款上。

Bromure Agentic Coding 不下那个注。它假设命令会执行、守卫有时会漏、而你请进门的 代理可能被一份你从未写过的 README 反过来对付你。所以它让命令执行的那个地方变得 不值得攻击:主机上的真密钥、箱子里的诱饵、一台可抛弃的 VM、一个留给没有 shell 能改写它的线路的硬性阻挡,以及一份所有试图离开之物的记录。你可以运行 GuardFall 破掉的那十款代理中的任何一款——无论哪一款,结果都是同一个可抛弃的箱子。它是自由 且开源的