AI 博客

你的智能体跑了一次 git status,这就够了

Manifold Security 披露了 GitSpawn——横跨七款 CLI 编码智能体的八个缺陷:打开一个被做了手脚的仓库就会运行攻击者的代码,因为外壳会派生 git 子进程去取上下文,而 Git 遵从了仓库提供的 core.fsmonitor 设置。没有提示词、没有审批,有时甚至在认证之前。9 月 1 日复测时仍有四个发现在执行,而你建起的每一道控制,都位于这件事早已发生的那个点的下游。

作者 智能体 AI 维基 26 分钟读完

你为编码智能体建起的每一道控制——审批弹窗、工具白名单、沙箱——都坐落在模型与世界之间,而 GitSpawn 压根不从模型旁边经过。Manifold Security 披露了一类横跨七款 CLI 编码智能体的八个缺陷:打开一个被做了手脚的仓库就是全部攻击——外壳跑一次 git status 来收集上下文,Git 遵从了仓库自己提供的 core.fsmonitor 设置,于是攻击者的命令在任何人敲下提示词之前,就以完整的用户权限执行了。研究者在 9 月 1 日复测时,八个中有四个仍在执行仓库提供的命令。

披露的是什么

Git 有一项叫 core.fsmonitor 的性能特性:把它指向一个辅助程序,Git 每次刷新索引时就会运行那个程序,从而免去一次完整的文件系统扫描。这项设置可以存在仓库自己的 .git/config 里。而被检视的几乎每一款 CLI 编码智能体,都会在会话开始时悄悄跑一次 git statusgit diff 之类的命令来弄清自己身处何处。这两个事实合起来,就成了不含任何漏洞利用的任意代码执行——配置被完全按设计读取,命令被完全按配置运行。

步骤开发者看到的实际发生的
仓库落到磁盘上 一个来自同事、客户或缺陷报告的文件夹 一份攻击者撰写的 .git/config 随之而来
智能体打开该目录 工具正在启动 外壳向 git 派生子进程以取上下文
Git 刷新索引 什么也没有——没有任何输出 core.fsmonitor 拉起攻击者的可执行文件
会话开始 一个提示符,等着你输入 代码已以登录用户的身份跑完了
The path the permission system covers, and the path GitSpawn uses Two lanes. The upper lane is the designed path: you type a request, the model proposes a tool call, a permission gate prompts or checks an allowlist, and the command runs inside a sandbox. The lower lane is the GitSpawn path: you open a folder, the harness runs git status to gather context before the model has a turn, git reads core.fsmonitor from the repository's own .git/config, and the attacker's command executes with full user privileges outside the sandbox with no prompt. Every control in the upper lane sits downstream of the point where the lower lane has already executed. Two paths into the same shell The path your controls cover You type a request and press enter The model proposes a tool call Permission gate prompt, allowlist, policy check Sandboxed execution Four opportunities to stop something. All of them downstream of the model. The path GitSpawn uses You open the folder with the agent The harness runs git status to gather context git reads the repo's own .git/config: core.fsmonitor The attacker's command runs No prompt typed. No approval clicked. Full user privileges, outside the sandbox. In some cases before the user has authenticated — so the model never had a turn to be governed. Everything in the upper lane is downstream of everything in the lower lane.
权限系统管的是模型决定去做什么。而这件事发生在模型还没轮到之前。

受影响的名单并非一堆边缘的业余项目:Claude Code、OpenAI Codex、Cursor、Grok Build、Goose、Hermes Agent 和 Qwen Code。七支独立的团队、七套独立的代码库、一个共有的盲区。

那道审批弹窗从来就不在这条路径上

读一遍过去一年的智能体安全文献,它们收敛到同一个思维模型:模型是不可信的决策者,所以在"它决定什么"和"实际发生什么"之间设一道关。审批工具调用。把命令列进白名单。把整套东西塞进容器。这些控制无一例外都锚在一个模型回合上——而对它所针对的那个威胁而言,这是个好模型。

GitSpawn 提醒我们:外壳不是中立的管道。在模型看到任何东西之前,外壳就在收集上下文:列文件、读配置、查分支、算 diff。那些活儿是你的智能体替你运行的代码,跑在你的账号里,输入由攻击者掌控,而这一整个品类没有任何产品把它当作一道有特权的边界。研究者指出,在某些情形下它甚至在用户完成认证之前就触发了——也就是说,那个跑掉了攻击者命令的进程,当时还没确立是谁在用它。

  • 审批弹窗无从触发,因为没有人提议过这个动作。这里没有工具调用,只有一个外壳每次都会跑的子进程。
  • 命令白名单会放行它。git status 在有史以来每一份白名单上,而危险行为在 Git 内部,不在参数向量里。
  • 沙箱往往还没起来。启动时的上下文收集经常跑在宿主进程里;而即便它跑在容器内,那个容器的范围也是照着智能体的工作划的,不是照着任意的攻击者代码划的。
  • 没有任何东西被记为可疑,因为按智能体自己的记账法,没发生任何可疑的事。成绩单是从损害之后才开始的。

这与拦截日志这条注入通道是同一个结构性论点的另一个方向。那边,攻击者的文本抵达了一个可信存储,智能体把它当作指令读了。这边,攻击者根本不需要模型配合——外壳就够了。两者都是"有意思的攻击面在循环周围的管道上,而不在循环里"。

git clone 是安全的,而这正是陷阱

Which ways of receiving a repository carry the payload Three columns comparing how a repository reaches a developer's disk. A normal git clone from a forge rebuilds .git/config locally and does not carry the attacker's setting, which is why the class looks unreachable. An archive, shared drive or synced folder copies the .git directory byte for byte and does carry it. A pre-built dev container or restored backup ships a working tree that was prepared elsewhere and carries whatever was in it, while also being the path a developer is least likely to inspect. How the repository reached your disk git clone from a forge Git rebuilds .git/config locally from your own defaults and the remote URL. Carries the payload: no. The setting never travels. Which is why the class reads as unreachable on first inspection. Archive or shared folder A zip, a tarball, a network share, a sync client — each copies .git byte for byte. Carries the payload: yes. Nothing rewrites the config. The ordinary way a bug report, a takehome or a client hands over code. Pre-built container / backup An image or a restore ships a working tree prepared on somebody else's machine. Carries the payload: yes. And is opened unread. The path a developer inspects least and trusts most.
人人脑中浮现的那条投递路径,恰恰是干净的那条。

从 GitHub 克隆不会携带载荷。Git 会用你自己的默认值和远端 URL 在本地构建 .git/config;源仓库的配置不会被传过来。正是这一个事实,让人们对这次披露的第一反应通常是"归到理论问题里去"——而这恰恰是它值得读第二遍的原因,因为那些确实会携带整个目录的投递路径,都平淡无奇。

  • 压缩包。一个工作树的 zip 或 tarball 会原样复制 .git。客户交付代码库是这样、笔试题送达是这样、仓库被附在支持工单上也是这样。
  • 共享盘与同步文件夹。网络共享、同步客户端、U 盘、挂进虚拟机的目录。它们都不会重写配置。
  • 预构建的开发容器与镜像。工作树是在别人的机器上准备好、整个发过来的。这也是开发者最不可能去检查、却最愿意信任的那条路径——因为"免去检查"本来就是镜像存在的意义。
  • 恢复的备份与快照。一次恢复会重现抓取时点的那份配置,包括更早一次入侵放进去的那份。

把这些和编码智能体的实际用法放在一起,暴露面就清晰了。这类智能体的日常就是被指向陌生代码——给这个仓库做分诊、复核这位外包的活儿、迁移这个遗留项目。"打开一个别人准备好的目录"对这个品类而言不是边缘情况,那就是本职工作。

更有用的产物是那份披露记录

GitSpawn findings by agent and patch status Eight findings across seven CLI coding agents. Goose was fixed and received CVE-2026-72718. The Claude Code core.fsmonitor finding was reported on 26 June and fixed in version 2.1.196 on 29 June. Cursor was fixed. Four findings were still executing repository-supplied commands when retested on 1 September: a second Claude Code configuration path, Grok Build, Qwen Code, and Hermes Agent, the last of which was assigned CVE-2026-71963 independently after six unanswered contact attempts. Eight findings, seven agents, four still open AGENT STATUS IDENTIFIER goose Fixed CVE-2026-72718 Claude Code — fsmonitor path Fixed in 2.1.196, three days after report closed as duplicate Cursor Fixed Claude Code — second path Still executing at the 1 September retest Grok Build Still executing at the 1 September retest Qwen Code Still executing at the 1 September retest Hermes Agent Still executing at the 1 September retest CVE-2026-71963 Hermes Agent's identifier was assigned independently after six unanswered contact attempts.
一半的发现被迅速关闭。另一半仍然敞着,而哪一半落在哪边并不随机。

Goose 发布了修复并拿到 CVE-2026-72718。Claude Code 的 core.fsmonitor 问题于 6 月 26 日报出、三天后由 2.1.196 修复——那份报告本身被作为"同一天更早提交的另一份报告的重复"关闭了,这说明有两位研究者在几小时之内发现了同一件事。Cursor 修好了自己那条路径。这是一套运转正常的披露流程。

另一半才是故事。9 月 1 日复测时,仍有四个发现在执行仓库提供的命令:Grok Build、Qwen Code、Hermes Agent,以及 Claude Code 的第二条配置路径。Hermes Agent 的 CVE-2026-71963 是在六次联系无人应答之后被独立分配的——一家已经发布了智能体、却联系不上安全对接人的厂商。

从这道分野里能取走的两件事,比漏洞本身更值得记:

  • 一家厂商打了补丁,不等于一整类问题被打了补丁。同一款智能体,一条路径三天修好,另一条两个月后仍然敞着。"我们修好了 GitSpawn"这种厂商层面的表述,撑不过一类八个发现的检验,钉死版本号的缓解措施也撑不过。
  • 安全成熟度如今是智能体工具的一条采购维度。一个项目有没有安全对接人、有没有公告订阅源、有没有"把一份报告变成一次发布"的记录,对于一件你要拿自己的凭据指向不可信代码的工具来说,不是加分项。它决定了你的暴露窗口,而在七款在功能对比表上看起来都差不多的产品之间,这项差别大得惊人。

真正有用的做法

即刻的修复属于厂商,而且并不精微:拒绝由仓库提供的可执行配置。Git 自带机制——safe.directory 会拦下不属于当前用户的仓库,而智能体在派生子进程时也可以强制指定自己的配置作用域。这个品类里的每一款智能体,在做上下文收集时都应当关掉仓库级配置对可执行设置的影响,并且应当在文档里写明这一点。

不必等待、你自己就能做的事更窄,但真实有效,而且能推广到这项设置之外:

  • 把"打开一个目录"当作那个有特权的动作。不是第一次工具调用——而是智能体被指向一棵目录树的那一刻。信任决策其实发生在那里,而目前没有任何产品会在那里弹窗。
  • 在用智能体打开非自己克隆的代码之前,先看一眼 .git/config对来路不明的目录树 grep 一遍 fsmonitorhooksPathsshCommandpagereditor。这是一道两行的、类似 pre-commit 的检查,覆盖了目前已知的那些形态。
  • 重新克隆,而不是就地打开。如果仓库是以压缩包形式到来的,就从权威远端克隆一份再把差异打上去。这在构造上就丢弃了攻击者掌控的配置——这是一次工作流改动,而不是一道控制。
  • 把陌生仓库放进一个已经起好的沙箱里跑。价值不在于沙箱有多聪明,而在于它在智能体进程启动之前就已存在,于是它能框住智能体自身权限系统永远看不见的那段启动代码。开发容器只有在镜像是你自己的时候才管用。
  • 盯住智能体子进程再派生出的进程。行为信号——git 拉起了一个不是 Git 子命令的东西——告警成本很低,而且熬得过下一个变体,因为下一个变体会换一个配置键。

更一般的教训比 core.fsmonitor 活得更久。编码智能体会派生子进程去调用的每一件工具,都有自己的配置语言,而其中好几种要么是图灵完备的,要么能指名一个待运行的程序:构建系统、测试运行器、linter、格式化工具、包管理器、编辑器。一个为了帮上忙而去读项目配置的智能体,执行的正是那个项目的意图。把模型当作唯一不可信组件的威胁模型,建模的是另一个系统。

常见问题

GitSpawn 是什么?

Manifold Security 披露的一类横跨七款 CLI 编码智能体的八个漏洞:仓库自己的 core.fsmonitor Git 设置,会在编码智能体执行一次寻常的后台 git status 时,运行一条攻击者选定的命令。不需要敲提示词,也不需要点审批;在某些情形下,它甚至在用户完成认证之前就触发了。

我只从 GitHub 克隆,会受影响吗?

沿这条路径不会。git clone 是在本地构建 .git/config 的,不会复制源仓库的那一份。暴露来自那些以压缩包形式抵达、放在共享盘或同步盘上、装在预构建开发容器里、或者经由恢复备份而来的仓库——任何复制而非重建 .git 目录的方式。

沙箱能保护我吗?

只能保护一部分,而且只有当它在智能体进程启动之前就已运行时才行。启动时的上下文收集经常在宿主进程里执行;即便它确实跑在容器内,那个容器的范围也是照着智能体的工作划的,不是照着任意攻击者代码划的。沙箱框住的是损害,它并不阻止那次执行。

哪些智能体已经修了?

Goose(CVE-2026-72718)、Cursor,以及 Claude Code 的 core.fsmonitor 路径——后者在 6 月 26 日报告后三天由 2.1.196 修复。四个发现——Grok Build、Qwen Code、Hermes Agent(CVE-2026-71963)以及 Claude Code 的第二条配置路径——在 9 月 1 日复测时仍在执行仓库提供的命令。去查你自己那个版本,而不是查厂商的一句声明。

这算提示词注入吗?

不算,而且这个区分很要紧。提示词注入是去说服一个模型做某件事;这件事从头到尾没有模型参与。它是通过一件外壳自作主张调用的可信工具而实现的任意代码执行——这正是为什么每一道瞄准"模型决定什么"的防御对它都无关。

最小可用的检查是什么?

在用智能体打开之前,对进来的工作树 grep 一遍 .git/config 里的 fsmonitorhooksPathsshCommandpagereditor。它覆盖了已知形态、不花什么成本,而且可以脚本化成你现有那层包装里的一个"打开前"钩子。

延伸阅读

本站相关:

  • 智能体外壳——为什么没人公布的那一半系统,通常正是出错的那一半。
  • 沙箱与执行——一道边界要在什么之前立起来才守得住。
  • 智能体供应链安全——这件事落在哪个品类里,以及那个品类其余部分的形状。
  • 环境权限——为什么被用掉的总是进程手里已经握着的那份凭据。
  • 爆炸半径——那个在你还不知道攻击原理时就能框住的属性。
  • 检测智能体被攻陷——那些熬得过"换一个配置键的变体"的行为信号。

来源: