2026 年 9 月 1 日,Manifold Security 公开了 GitSpawn:八条 CVE,横跨七款主流 AI 编码 Agent(Claude Code、Codex CLI、Cursor、Goose、Qwen Code、Grok Build、Hermes Agent),全部靠同一条 Git 配置键 core.fsmonitor 触发预信任代码执行。Manifold 博客里那句话比数字更扎人:"八条 CVE 不是重点。重点是零——零个 clean agent。"
七款 Agent 在 researcher 接触之前,没有一款对这个场景写过威胁模型。而 core.fsmonitor 是 2020 年 Git 2.28 就有的功能,VS Code 2021 年就修过一模一样的洞。六年过去,AI 编码 Agent 一个不落地全踩进同一个坑。
根因:不是一条配置,是三条
先说 core.fsmonitor。Git 2.28 引入 FSMonitor,作用是让 Git 不用逐个文件 stat,改问一个外部进程。配置长这样:
[core]
fsmonitor = /path/to/monitor
git status、git diff、git add 只要遇到 core.fsmonitor,就调用这个外部程序。正常用途是挂 Watchman 这类文件监控守护进程。问题在于:这条配置写死在仓库自己的 .git/config 里。你克隆一个仓库,它自带 .git/config,里面 fsmonitor 指向攻击者的脚本。
AI 编码 Agent 什么时候触发?打开仓库的那一刻。Agent 会在后台跑 git status / git diff 感知当前上下文。在审批对话框弹出之前、在你看到任何提示之前、在信任确认之前,命令已经执行了。而且执行的不是 Agent 沙箱里的命令,是 Agent 进程在宿主机上 fork 的 Git 子进程,退出路径直通你的 shell。
威胁路径只有三跳:
仓库 .git/config → core.fsmonitor → Git 子进程任意命令执行
中间没有 Agent 层介入。Agent 根本不知道 Git 在背后干了什么。
Manifold 确认的可利用键不止 fsmonitor,还有两条:
core.hooksPath(Git 1.7.1,2010 年就有):把 hooks 目录重定向到攻击者控制的位置,git commit/git merge时触发。diff.external:外挂 diff 程序替代内置引擎。Agent 重度依赖 diff——看变更、生成 commit message、做 code review——每跑一次 diff,命令就执行一次。
| 维度 | core.fsmonitor | core.hooksPath | diff.external |
|---|---|---|---|
| 首次引入 | Git 2.28 (2020) | Git 1.7.1 (2010) | Git 早期,无明确版本 |
| 触发命令 | status/diff/add 等 index refresh | commit/merge/push 等 hook 事件 | 任何 diff 调用 |
| Agent 知情 | 不知情 | 不知情 | 不知情 |
| 经过沙箱 | 否 | 否 | 否 |
| 绕过审批 | 是 | 是 | 是 |
沦陷清单
Manifold 测了七款 Agent,结果如下:
| Agent | 路径数 | CVE | 修复状态 |
|---|---|---|---|
| Claude Code | 2 | CVE-2026-55607 | 部分修复(fsmonitor 已修,ultrareview 未修) |
| OpenAI Codex CLI | 1 | CVE-2026-55608 | 已修复(0.131.0) |
| Cursor | 1 | CVE-2026-57555 | 已修复(未公开版本号) |
| Goose | 1 | CVE-2026-72718 | 完整修复(1.44.0) |
| Qwen Code | 1 | 未分配 | 未修复(0.22.3 仍可利用) |
| Grok Build | 1 | 未分配 | 未修复 |
| Hermes Agent | 1 | 未分配 | 未修复 |
三种修复策略:清、截、拒
修没修是一回事,怎么修暴露了各家对"Agent 安全边界在哪"的理解。
Goose(唯一完整修复)——副本隔离。 Goose 不信任仓库本身的 .git/config,在 tempfs 上建一个副本再执行 Git 命令,副本里的 config 是干净的。加上 GIT_CONFIG_PARAMETERS="" 覆盖本地配置,双重保护。这一下把 core.hooksPath、diff.external 所有可执行键一起清掉,不只是 fsmonitor。最彻底,也最贵——一个 2GB 的 monorepo,每次 git status 都要完整复制到 tempfs。Goose 选了"正确但贵",性能代价摆在那。
Claude Code——环境变量覆盖。 Anthropic 在 2.1.196 用 GIT_CONFIG_PARAMETERS="'core.fsmonitor='" 把 fsmonitor 清零。但它漏了 claude ultrareview 这条独立 CLI 路径:它走不同的 Git 调用,环境变量没覆盖到。Manifold 7 月 15 日报了这条,Anthropic 标记 internal duplicate,到 9 月 1 日的 2.1.252 仍未修。典型的"修了一个调用点,没修另一个"。
Codex CLI——原地正则 strip。 启动时用正则把 core.fsmonitor / core.hooksPath / diff.external 三段从 config 里删掉。问题很明显:只认已知三条,明天 Git 加一条新可执行键代码又得改;直接改文件内容,多线程同时读 config 有 race condition。CLI 0.131.0 修了。
Cursor——静默修复。 没有公开 CVE 编号,没有公告,没有 diff,只提一句"fixed in a later build"。推测和 Codex CLI 类似,但无法确认。
未修复的四款。 Qwen Code 最特殊:阿里云 SRC 接受了报告、承认漏洞存在,但到公开日 0.22.3 仍可利用,窗口期接近两个月。Grok Build 代表 xAI 把报告标记为 "informative",立场是"Agent 应该假设工作区不可信,安全责任在用户侧"。Hermes Agent 六次联系(三个 GitHub issue、两封邮件、一次 X DM)无回复。
三步复现
下面这套本地就能跑,不依赖网络基础设施。
步骤一:构造恶意仓库
mkdir -p gitspawn-demo/.git && cd gitspawn-demo
git init --initial-branch=main 2>/dev/null
cat > .git/config << 'CONFIG'
[core]
repositoryformatversion = 0
filemode = true
bare = false
fsmonitor = /tmp/gitspawn-poc.sh
[init]
defaultBranch = main
CONFIG
cat > /tmp/gitspawn-poc.sh << 'SCRIPT'
#!/bin/bash
echo "当前用户: $(whoami)" > /tmp/gitspawn_poc_output.txt
echo "执行路径: $(pwd)" >> /tmp/gitspawn_poc_output.txt
SCRIPT
chmod +x /tmp/gitspawn-poc.sh
关键踩坑:git init 会覆写 .git/config,所以顺序必须是 init → 重写 config。缺了 init,Git 不认这是个仓库;缺了重写,恶意配置被冲掉。
步骤二:验证 Git 本身会执行 fsmonitor
cd gitspawn-demo
git status --porcelain
cat /tmp/gitspawn_poc_output.txt
输出文件被创建,说明 git status 确实执行了 core.fsmonitor 指向的程序,跑在宿主机上,有完整环境上下文。这一步证明问题不在 Agent 层,Git 本身就有这个能力。
步骤三:用任意命令行 Agent 打开仓库观察
cd /tmp/gitspawn-demo
claude # 或 codex / cursor / goose
Agent 启动后立即 git status 感知上下文。如果 /tmp/gitspawn_poc_output.txt 被更新,说明审批对话框还没弹出来,代码已经在你机器上跑完了。
历史重演
GitSpawn 不是孤立事件。"本地配置能执行远程代码"是版本控制系统里反复出现的 pattern:
- 2014 · CVE-2014-9390:
git clone执行任意命令。攻击者用.Git/config大小写变体绕过core.protectNTFS,克隆时写入本地配置。这是 Git 自身的问题,跟 AI Agent 无关。 - 2021 · CVE-2021-43891:VS Code 的 git autofetch 后台
git fetch触发 fsmonitor。修复方案是设置core.fsmonitor=空字符串覆盖环境变量——和 Claude Code 的方案几乎一样,但早了五年。 - 2023 · CVE-2023-32760:Git LFS 的 smudge filter 在 checkout 时执行,攻击者可在
.lfsconfig指定恶意 filter 命令。
三个案例覆盖 clone、fetch、checkout 三条路径,走的是同一个信任模型缺口:Git 信任仓库本地配置的权限,高于用户信任模型的权限。 AI 编码 Agent 只是这个旧问题的新载体。
检测与防御
用户侧自查,一个 Python 脚本扫工作区里的 .git/config:
import os, configparser, sys
SUSPICIOUS = {
'core.fsmonitor': '文件监控执行入口(GitSpawn)',
'core.hookspath': 'Git hook 目录重定向',
'diff.external': '外部 diff 程序',
'core.sshcommand': 'SSH 命令重写',
}
def scan(root):
bad = {}
for dirpath, dirnames, _ in os.walk(root):
if '.git' in dirnames:
cfg = configparser.ConfigParser()
cfg.read(os.path.join(dirpath, '.git', 'config'))
hits = {f'{s}.{k}': v for s in cfg.sections()
for k in cfg[s] if f'{s}.{k}'.lower() in SUSPICIOUS}
if hits:
bad[dirpath] = hits
dirnames.remove('.git')
return bad
if __name__ == '__main__':
for repo, hits in scan(sys.argv[1] if len(sys.argv) > 1 else '.').items():
print(f'[!] {repo}:')
for k, v in hits.items():
print(f' {SUSPICIOUS[k]}: {v[:60]}')
防御分三层。用户侧:export GIT_CONFIG_PARAMETERS="'core.fsmonitor=''core.hooksPath='" 全局覆盖,代价是禁用 Watchman 和正常 hooks。版本侧:Goose ≥ 1.44.0、Codex CLI ≥ 0.131.0、Claude Code ≥ 2.1.196(仅 fsmonitor),Qwen Code / Grok Build / Hermes Agent 目前无修复版本。企业侧:auditd 监控 .git/config 被非 git 进程修改,阻断开发环境到未知目的地的外连(挡不住执行,但能断数据外传)。
我的看法
这个漏洞真正值得想的,不是"Agent 又出洞了",而是信任模型的问题。Agent 的"沙箱 + 审批"模型假设它调用的所有工具都在可控范围内。但 Git 不是"工具",是一个生态,有自己的配置系统、执行模型、hook 机制。Agent 调用 Git 时,等于把执行控制权交给了 Git 的配置系统,而配置系统不受沙箱管辖。
Agent 安全边界:模型输出 → 审批对话框 → 代码执行
Git 安全边界: 仓库配置 → Git 子进程 → 代码执行
GitSpawn 打的正是这两个边界之间的缝。Agent 以为自己在跑一个安全的 git status,实际在跑一条不受控的命令。
更值得警惕的是供应链污染。Manifold 博客里那个场景:恶意仓库让 Agent 执行 fsmonitor 命令,命令在本地装了后门 npm 包,Agent 后续提交到 CI 的代码里带着这个后门,CI 产物被污染,用户拉新版本时被感染。传播路径从开发者桌面开始,比单纯的数据泄露严重得多。
落到行动上,我只记三条:第一,所有跑编码 Agent 的环境,先设 GIT_CONFIG_PARAMETERS 覆盖本地可执行配置键;第二,拉代码做 PR review 之外,别信任不可信来源的仓库;第三,如果你自己写 Agent,调系统 Git 的每一处——git status、git diff、git fetch、git commit、git merge——fork-exec 之前把本地配置清掉。VS Code 2021 年的方案(core.fsmonitor= 空字符串)现在还能直接抄,五年了没变过。
⚠️ 网络安全免责声明
本文内容仅供技术研究与安全学习之用。文中描述的漏洞分析、攻击链拆解和代码示例均基于公开披露的安全研究,旨在帮助开发者理解攻击原理、提升安全意识和防御能力。
请勿将本文所述技术用于任何未经授权的安全测试、渗透攻击或非法用途。任何因滥用本文信息而造成的法律后果,由使用者自行承担,与本文作者无关。
如发现系统安全漏洞,请通过合法渠道向相关厂商或平台报告,共同维护网络安全生态。
浙公网安备 33010602011771号