摘要: Plugin4Shell 是 AIR Security 在 2026 年披露的一类 AI Coding Agent 插件供应链漏洞:插件市场虽然把插件固定到经过审核的 Git Commit SHA,客户端却只执行 git checkout <pin>,没有在检出后验证真实 HEAD。攻击者控制上游仓库后,可利用 Git 引用解析歧义,让 Claude Code、Codex、GitHub Copilot 或 Gemini CLI 安装与页面所示 SHA 不一致的代码。本文完整复盘两条攻击链、零点击更新条件、四款工具的差异、修复状态及企业排查方案。核心结论是:SHA Pinning 只是“期望值”,只有对象级抓取、明确 detached checkout、检出后 HEAD 校验、内容摘要与签名验证全部成立,固定版本才真正形成安全边界。
核心结论
Plugin4Shell 不是 SHA-1/Commit Hash 被破解,也不是攻击者改写了已有 Git 对象;它利用的是“安全策略记录了一个 SHA,但客户端没有验证最后运行的工作树确实来自这个 SHA”。因此,写入 Pin、执行 Checkout 和验证最终结果是三件不同的事,缺少最后一步就会产生“界面显示可信版本、主机执行恶意版本”的完整性错觉。
- 共同根因: 四款工具都信任 Git 的名字解析或中间引用,却没有在检出后比较
git rev-parse HEAD与预期 Pin。 - 两种变体: Claude Code、Codex、Copilot 可被 40 位十六进制默认分支名制造歧义;Gemini CLI 的研究复现使用名为
FETCH_HEAD的默认分支劫持 Checkout。 - 零点击条件: 已安装插件、市场 Pin 发生更新、客户端后台自动更新,以及攻击者控制插件源仓库;不是所有安装都必然满足全部条件。
- 修复状态需分层表述: AIR 报告称 Claude Code 2.1.179 与 Codex 0.146.0 已修复,Copilot 未发布修复,Gemini CLI 不再修复;截至本文核验日,没有找到四家统一格式的公开安全公告或 CVE,应以各厂商当前版本与安全渠道再次确认。
- 企业正确策略: 立即盘点 Agent、插件、市场源与自动更新;升级已有修复的客户端;对无可验证修复的产品暂停第三方插件与自动更新,并使用内部镜像、签名制品、无网络沙箱和运行时审计。
Plugin4Shell 发生了什么
结论:这是“Pin 解析与执行结果不一致”的插件安装器漏洞,而不是模型被提示注入。 AIR Security 报告称,其研究团队在 2026 年 5 月发现问题,6 月向四家厂商协调披露,并用 PoC 复现了 Claude Code、OpenAI Codex、GitHub Copilot 与 Gemini CLI 的插件安装/更新链路。
一个典型插件市场会审核仓库某次提交,把 40 位 Commit SHA 写入清单。用户看到的安全承诺是:“无论上游分支以后怎样变化,客户端都只安装这个不可变 Commit。”这项承诺成立需要三个环节:
- 市场清单中的 Pin 不能被未授权修改;
- 客户端必须取得该对象并以无歧义方式检出;
- 安装或执行前必须证明实际工作树、制品和预期对象一致。
Plugin4Shell 击穿的是第二、第三环节。AIR 的复盘显示,受影响安装器向 Git 传入一个看似精确的 SHA 或 FETCH_HEAD,但 Git 可能把字符串解析成攻击者控制的引用。由于客户端只检查 Checkout 是否成功,没有读取最终 HEAD 做等值断言,恶意代码便被当作固定版本安装。
该问题尤其危险,因为 Coding Agent 插件往往能包含命令、Hook、MCP Server、脚本、上下文指令或安装生命周期逻辑,并以开发者账号访问源码、SSH Agent、云 CLI、环境变量与本地文件。成功利用后的影响取决于客户端权限、沙箱、网络和凭据,而不是由“插件”这个名称天然限制。
想系统理解相关风险,可结合 AI Stack Nav 的 Coding Agent 安全检索 与 AI Agent 供应链安全检索 一并评估。

第一条攻击链:40 位 SHA 形状的默认分支
结论:Git 接受“长得像完整 Commit SHA 的分支名”,而普通 Checkout 可能优先选择同名引用。 AIR 报告称,这一变体影响研究时的 Claude Code、Codex 和 GitHub Copilot 插件流程。
安装器的逻辑可以简化为:
git clone <PLUGIN_REPOSITORY> ./plugin
cd ./plugin
git checkout aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
开发者直觉上会认为,第二行只能定位对象 ID aaa...aaa。然而,如果攻击者在仓库中创建同名分支,并把它设为默认分支,普通 git clone 会把默认分支带成本地分支。此时 git checkout 面对一个既可能是引用、又可能是对象前缀/对象 ID 的字符串,可能优先解析引用,只输出“refname is ambiguous”一类警告,而命令仍成功。
AIR 给出的完整步骤是:攻击者先发布无害插件并通过审核;市场把版本 Pin 到 Commit A;用户安装;随后市场正常升级到仍然无害的 Commit B;攻击者取得或本来就控制源仓库,创建一个名称等于 Commit B 完整 SHA 的默认分支,并把分支指向恶意代码。客户端后台更新再次执行普通 Checkout,最终进入同名分支,而不是 Commit B。
这里有两个重要边界。第一,分支必须能被托管平台创建并作为默认分支;AIR 指出 GitHub 会拒绝 40 位十六进制分支名,但 Bitbucket 和某些自托管 Git 服务允许,因此不能笼统宣称“所有 GitHub 插件都可直接利用”。第二,攻击者通常需要控制插件仓库或接管可信作者仓库,Plugin4Shell 本身不会凭空取得上游控制权。
第二条攻击链:Gemini CLI 的 FETCH_HEAD 歧义
结论:Gemini CLI 研究变体不是 40 位分支名,而是把 Git 的伪引用名称变成攻击者默认分支。 AIR 报告把受影响流程概括为:
git clone --depth 1 <PLUGIN_REPOSITORY> ./plugin
cd ./plugin
git fetch origin 41d0bc0a4aeb2fbf797dacea39e876d98c95024b
git checkout FETCH_HEAD
git fetch 确实获取了正确 Commit,并把信息写进 .git/FETCH_HEAD。危险发生在下一步:如果攻击者把仓库默认分支命名为 FETCH_HEAD,Checkout 的名称解析可能落到本地分支,而不是刚才 Fetch 生成的伪引用文件。于是“正确对象已经下载”并不能保证“正确对象最终进入工作树”。
这条链说明,安全审计不能只检查网络请求是否包含预期 SHA,也不能只看到 .git/FETCH_HEAD 中出现过目标对象就判定成功。必须从最终执行目录读取 HEAD^{commit},并与经过规范化的预期对象逐字比较。
为什么 SHA Pinning 仍然会失效
结论:Pinning 只固定输入,不自动验证输出。 下面六个断点中,任何一个失守都可能使“固定 SHA”变成表面安全:
| 断点 | 错误假设 | 实际风险 | 应有控制 |
|---|---|---|---|
| 名称解析 | 40 位十六进制必定是对象 | 同名分支/标签可能被优先解析 | 使用对象级语法并拒绝歧义引用 |
| Fetch | 下载了目标对象就安全 | 工作树可能检出另一个引用 | 校验最终 HEAD^{commit} |
| Checkout | 命令返回 0 即成功 | 成功只表示某个对象/引用已检出 | 比较实际 SHA,警告视为失败 |
| 工作树 | HEAD 正确即内容可信 | Smudge Filter、子模块、LFS、生成步骤可能引入内容 | 禁用危险过滤器,递归固定依赖 |
| 构建制品 | 源码 Pin 等于制品 Pin | 安装脚本和运行时下载可改变执行内容 | 生成物哈希、签名与可复现构建 |
| 执行权限 | 插件可信即可全权限运行 | 上游接管会继承 Agent 权限 | 沙箱、无网、最小凭据与审批 |
Plugin4Shell 的直接根因是名称解析与缺少后验校验,但标题中的“为什么 SHA Pinning 仍会失效”还应覆盖更广的供应链边界。即使修复了本次 Git 歧义,只校验顶层 Commit 也不能保证子模块、Git LFS 对象、包管理器依赖、安装脚本、容器基础镜像和插件运行时下载都不可变。真正需要固定的是“完整执行闭包”,而不是仓库入口处的一个字符串。
四款工具差异与当前修复状态
结论:四款工具共享安全设计错误,但利用细节与修复信息不能混写。 以下状态以 AIR Security 披露页面为主要依据,并用公开仓库/文档核对产品现状;由于未发现完整的厂商 CVE/安全公告链,生产决策仍需向厂商支持渠道确认。
| 工具 | AIR 报告的变体 | AIR 报告的状态 | 截至核验日的处理建议 |
|---|---|---|---|
| Claude Code | 40 位 SHA 同名默认分支 | 2.1.179 修复 | 升级到当前稳定版,不要只停在最低修复版 |
| OpenAI Codex | 40 位 SHA 同名默认分支 | 0.146.0 修复 | 升级当前稳定版并重新安装/核验插件 |
| GitHub Copilot | 40 位 SHA 同名默认分支 | 报告称未发布修复 | 暂停不可信市场插件与自动更新,向厂商确认 |
| Gemini CLI | FETCH_HEAD 默认分支歧义 |
报告称不再修复并建议迁移 | 不依赖单一报道直接决策;核对官方产品生命周期,无法确认时隔离或停用插件 |
Claude Code 的公开 Releases 页面显示,核验日的最新版已经明显高于 2.1.179,因此用户不应把 2.1.179 误解为“最新版本”。同样,Codex 0.146.0 是 AIR 报告的最低修复点,而不是永久推荐版本。安全升级应使用厂商当前稳定渠道,并验证组织中的 IDE 扩展、桌面端、CLI 与远程 Worker 是否都捆绑了新版本。
对于 Gemini CLI,Google 的官方 GitHub 仓库在本文核验时仍公开提供安装、稳定/预览/夜间发布说明以及最新文档,这与 AIR 页面“已弃用且不会修复”的表述存在明显需要复核之处。因此本文把该说法严格标注为研究方陈述,不将其写成已由 Google 官方公告确认的事实。企业应向 Google 安全或支持渠道索取明确版本和修复结论。
零点击为什么成立,又有哪些前提
结论:零点击描述的是更新阶段无需用户再次确认,不等于攻击者无需准备条件。 研究报告指出,Claude Code 与 Codex 的插件后台自动更新会在 Pin 变化后重新触发 Checkout;用户已经安装插件后,不需要再次点击安装,恶意工作树便可能落地并执行。
要形成完整攻击通常需要:
- 目标使用支持市场插件的受影响客户端版本;
- 已安装来自可被攻击者控制或接管的插件仓库;
- 托管平台允许相应的恶意默认分支名称;
- 市场清单或更新流程触发新的 Pin/重新安装;
- 客户端自动更新或其他无交互路径执行 Checkout;
- 插件生命周期能够执行代码,且沙箱、网络或凭据权限足以产生影响。
缺少其中某些条件,攻击可能降级为需交互安装、无法复现或影响受限。安全文章不应把“研究者完成端到端 PoC”写成“互联网所有实例都已被利用”。截至核验日,公开资料未提供真实攻击活动、受害企业数量或统一 CVE 的确证,因此本文不引用“数百万已被攻陷”等无法独立验证的数字。
正确修复:验证最终对象,而不是请求字符串
结论:最小必要修复是在执行任何插件代码前,解析最终 HEAD 并与期望的完整 Commit SHA 进行常量时间意义上的精确比较;不一致立即失败。 AIR 给出的核心断言是:
expected_sha="FULL_40_HEX_COMMIT_SHA"
actual_sha="$(git rev-parse --verify HEAD^{commit})"
if [ "$actual_sha" != "$expected_sha" ]; then
echo "Plugin integrity check failed" >&2
exit 1
fi
生产实现还应增加:验证 Pin 必须是完整 40 位十六进制;拒绝仓库中与 Pin 或保留伪引用同名的危险引用;使用 git fetch <remote> <sha> 后显式 detached checkout;禁用 Git Hooks、全局 Filter 和不必要的 LFS/子模块;在干净目录执行;将警告视为失败;验证工作树无修改;对最终制品做内容哈希和签名校验。
更稳妥的流程示意:
set -euo pipefail
repo_url="https://YOUR_APPROVED_HOST/ORG/PLUGIN.git"
expected_sha="FULL_40_HEX_COMMIT_SHA"
install_dir="./isolated-plugin-dir"
case "$expected_sha" in
(*[!0-9a-f]*|'') echo "Invalid SHA" >&2; exit 1 ;;
esac
[ "${#expected_sha}" -eq 40 ] || exit 1
git -c core.hooksPath=/dev/null init "$install_dir"
git -C "$install_dir" remote add origin "$repo_url"
git -C "$install_dir" fetch --no-tags --depth=1 origin "$expected_sha"
git -C "$install_dir" checkout --detach "$expected_sha"
actual_sha="$(git -C "$install_dir" rev-parse --verify HEAD^{commit})"
[ "$actual_sha" = "$expected_sha" ] || exit 1
[ -z "$(git -C "$install_dir" status --porcelain)" ] || exit 1
该示例用于说明控制点,不是跨所有 Git 版本与插件格式的万能安装器。企业还需要验证 URL 重定向、证书、代理、子模块、LFS、包依赖、构建脚本与最终发布物。
企业紧急排查与处置步骤
结论:先切断自动更新和不可信来源,再升级、重装、核验与轮换凭据。 仅升级客户端并不能自动证明旧插件从未运行过恶意代码。
- 盘点资产: 列出 Claude Code、Codex、Copilot、Gemini CLI 的所有版本、安装方式、IDE 捆绑版本、远程 Worker 与用户范围。
- 导出插件清单: 记录插件名、市场、仓库 URL、Pin、安装目录、自动更新状态、首次与最后更新时间。
- 立即隔离高风险源: 暂停来自允许 SHA 形状分支或自托管源的自动更新;无法验证的插件先禁用。
- 升级客户端: Claude Code 和 Codex 升级到厂商当前稳定版且不低于 AIR 报告的修复点;其他产品向厂商确认。
- 检查危险引用: 在内部镜像侧拒绝 40 位十六进制分支名、
FETCH_HEAD等保留名称以及异常默认分支变更。 - 重新解析并验证: 对每个 Pin 在隔离环境重新 Fetch,比较最终
HEAD^{commit},生成文件清单和内容摘要。 - 全量重装插件: 从清洁镜像和已验证制品重新安装,不直接信任现有缓存与工作树。
- 调查主机活动: 搜索异常子进程、网络连接、启动项、SSH/云凭据访问、Shell 历史、插件 Hook 与新增二进制。
- 轮换可能暴露的凭据: 以插件执行权限为界,轮换 Git、包仓库、云、MCP、数据库和签名密钥;先吊销后补发。
- 恢复与监控: 分批恢复插件,启用安装日志、出网控制、行为检测和 Pin-to-HEAD 持续校验。

插件市场与企业治理如何补强
结论:市场审核不能替代客户端验证,但可以降低上游接管与恶意更新概率。 AIR 正确指出,最终 Pin 在客户端解析,市场无法单独保证 Checkout 结果;不过企业仍应建立内部允许列表、镜像与制品签名,让攻击者难以直接控制更新链。
建议采用四层控制:
- 来源层: 只允许组织审核的仓库与发布者,强制 MFA、分支保护、签名提交、所有权与离职回收。
- 解析层: 企业代理负责 Fetch、无歧义检出、Pin-to-HEAD 校验,拒绝危险引用和 URL 重定向。
- 制品层: 将插件打包为不可变制品,生成 SBOM、内容摘要与签名,通过内部 Registry 分发。
- 运行层: 插件默认无网络、只读工作区、无主机 Secrets;Shell、外发、权限和生产操作必须人工批准。
插件更新不应直接从作者仓库进入开发者主机。更可靠的管线是:上游发现新版本 → 隔离构建 → 静态/动态扫描 → 人工 Diff → 签名制品 → 灰度 → 企业客户端按内容摘要安装。自动更新可以保留,但自动的是“已通过企业验证的不可变制品”,而不是实时执行上游 Git Checkout。
如何证明你的修复真的有效
结论:必须用对抗性测试验证安全属性,而不是只看升级成功。 至少建立以下回归用例:
- 创建与 Pin 完全相同的 40 位十六进制默认分支,安装必须失败或仍落到固定对象;
- 创建默认分支
FETCH_HEAD,最终HEAD必须保持目标 Commit; - 使用同名标签、短 SHA、大小写异常和 Unicode 相似字符测试解析;
- 改变默认分支、仓库重定向、所有权和远端 URL,客户端必须告警;
- 配置全局 Git Filter、Hook、子模块和 LFS,验证不会绕过内容摘要;
- Pin 校验失败、网络中断、警告或日志服务不可用时必须 Fail Closed;
- 自动更新前后记录 Pin、最终 HEAD、文件哈希、签名、工具版本与审批人;
- 恶意插件尝试读取
.env、SSH Agent、云元数据和外网,沙箱与策略必须阻断。
不要把 git checkout 返回码、日志中出现目标 SHA 或市场页面显示 Pin 当作测试成功。验收对象是最终执行目录和制品,并且要在运行前、运行时、每次更新后重复验证。
风险、限制与事实边界
结论:这起事件的技术机制有清晰复现描述,但供应商状态与影响规模仍需谨慎。 AIR Security 是漏洞发现与披露方,同时销售 Agent 安全产品;其技术细节值得研究,但厂商状态、用户规模和“不受影响”陈述仍应与各厂商独立核对。
截至 2026 年 9 月 25 日,本文未检索到一个可覆盖四款产品的官方 CVE,也未发现 Anthropic、OpenAI、Microsoft、Google 都发布了内容一致的安全公告。AIR 报告的版本和时间线因此标注为“研究方报告”;Claude Code 官方 Releases 可证明当前版本远高于所述修复点,但不等同于官方在发布说明中确认 Plugin4Shell 名称和漏洞细节。
此外,插件系统、自动更新行为和客户端架构可能快速变化。Copilot 的“插件”范围、Gemini CLI 的扩展机制、不同企业分发渠道也可能与研究环境不同。任何处置前都要核对自身实际使用的产品、版本、插件格式和更新路径,不能仅凭产品名称判定已受攻击。
事实依据与来源
攻击机制、PoC 条件、两种 Git 解析变体、发现与协调披露时间线,以及 Claude Code 2.1.179、Codex 0.146.0 等修复状态,来自 AIR Security 的原始研究披露。Help Net Security 对核心机制进行了独立报道,但其修复状态仍主要引用研究方信息。
Claude Code 当前版本与发布动态来自 Anthropic 官方 GitHub Releases;Gemini CLI 当前公开仓库、安装渠道、发布通道、Shell 能力和 Policy Engine 来自 Google 官方 GitHub 仓库及文档。本文关于“Gemini CLI 已弃用”的矛盾处理属于编辑核验:官方仓库仍展示活跃发布信息,因此没有把研究方表述升级为 Google 官方事实。
企业四层治理、十步处置、强化安装脚本与回归测试属于实施建议。本文没有执行公开 Exploit、没有扫描用户设备,也没有证据证明该漏洞已在真实攻击中被利用;是否受影响需要组织结合插件源、版本、日志与主机取证判断。
FAQ
Plugin4Shell 是 Git SHA-1 碰撞攻击吗?
不是。攻击者不需要生成相同哈希的两个 Commit,也不需要改写既有对象。漏洞利用 Git 对引用名与对象名的解析歧义,以及客户端没有验证最终 HEAD,属于 Pinning 实现错误。
只从 GitHub 托管仓库安装插件就绝对安全吗?
不能说绝对安全。AIR 指出 GitHub 拒绝 40 位十六进制分支名,可缓解第一种变体,但它不能覆盖 Gemini 的 FETCH_HEAD 变体,也不能解决仓库接管、恶意依赖、安装脚本、签名缺失与运行时下载等其他风险。
Claude Code 和 Codex 应升级到哪个版本?
AIR 报告的最低修复点分别是 Claude Code 2.1.179 和 Codex 0.146.0。实际操作应升级到厂商当前稳定版,而不是停留在最低修复点,并检查 IDE 扩展、远程 Worker 和缓存插件是否同步更新。
Copilot 与 Gemini CLI 目前是否已经修复?
AIR 报告称 Copilot 未发布修复,Gemini CLI 不会修复;但截至核验日没有找到与该表述完全对应的两家公开安全公告,且 Gemini CLI 官方仓库仍显示活跃发布。因此企业必须向厂商确认,确认前应暂停第三方插件自动更新并进行隔离。
关闭自动更新能完全消除风险吗?
不能。它能阻断部分零点击更新路径,但首次安装、手动更新、受污染缓存和既有恶意插件仍有风险。完整处置需要升级客户端、重新验证与安装插件、调查主机并轮换可能暴露的凭据。
检查 git rev-parse HEAD 就足够了吗?
它能修复本次漏洞的核心缺失校验,但不是完整供应链保证。还要验证子模块、LFS、依赖、安装脚本、构建制品、签名和运行时下载,并限制插件执行权限与网络。
为什么警告没有阻止攻击?
Git 在引用歧义时可能输出警告但仍返回成功,客户端若只检查退出码就会继续安装。安全敏感解析应把歧义警告当作失败,并在执行前进行最终对象等值校验。
已升级客户端后还要轮换凭据吗?
如果曾安装来自可疑市场、被接管仓库或无法验证的插件,并且插件可能在旧版本中运行过,就应按其可触达权限进行取证和凭据轮换。仅升级不会撤销已泄露的 Token 或清除持久化。
企业能否继续使用自动插件更新?
可以,但更新来源应改为内部验证和签名后的不可变制品。上游更新先经过隔离构建、Diff、扫描、Pin-to-HEAD 校验与灰度,再由客户端自动拉取内容摘要固定的企业制品。
参考来源
- AIR Security:Plugin4Shell 原始研究披露
- Help Net Security:Plugin4Shell 报道
- Anthropic Claude Code 官方 Releases
- Google Gemini CLI 官方仓库
- Gemini CLI Policy Engine 官方文档
- Gemini CLI Shell Tool 官方文档
内容核验日期:2026 年 09 月 25 日
环境配置与 Docker 工作流
适合阅读安装部署、本地配置、服务器搭建和自动化流程类文章后继续转化。