Plugin4Shell 安全复盘科技封面,展示四类 Coding Agent 插件穿透失效的 SHA Pinning 防线

Plugin4Shell 完整复盘:Claude Code、Codex、Copilot、Gemini CLI 为什么 SHA Pinning 仍会失效?

Plugin4Shell 利用 Git 引用歧义绕过插件 Commit 固定,本文复盘四款 Coding Agent 的攻击链、修复边界与企业处置方案。

摘要: 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。”这项承诺成立需要三个环节:

  1. 市场清单中的 Pin 不能被未授权修改;
  2. 客户端必须取得该对象并以无歧义方式检出;
  3. 安装或执行前必须证明实际工作树、制品和预期对象一致。

Plugin4Shell 击穿的是第二、第三环节。AIR 的复盘显示,受影响安装器向 Git 传入一个看似精确的 SHA 或 FETCH_HEAD,但 Git 可能把字符串解析成攻击者控制的引用。由于客户端只检查 Checkout 是否成功,没有读取最终 HEAD 做等值断言,恶意代码便被当作固定版本安装。

该问题尤其危险,因为 Coding Agent 插件往往能包含命令、Hook、MCP Server、脚本、上下文指令或安装生命周期逻辑,并以开发者账号访问源码、SSH Agent、云 CLI、环境变量与本地文件。成功利用后的影响取决于客户端权限、沙箱、网络和凭据,而不是由“插件”这个名称天然限制。

想系统理解相关风险,可结合 AI Stack Nav 的 Coding Agent 安全检索 与 AI Agent 供应链安全检索 一并评估。

Plugin4Shell 两条 SHA Pinning 绕过攻击链,展示 SHA 分支歧义和 FETCH_HEAD 分支劫持
Plugin4Shell 两条攻击链:请求的是固定 SHA 或 FETCH_HEAD,Git 最终检出的却可能是攻击者控制的默认分支。

第一条攻击链: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;用户已经安装插件后,不需要再次点击安装,恶意工作树便可能落地并执行。

要形成完整攻击通常需要:

  1. 目标使用支持市场插件的受影响客户端版本;
  2. 已安装来自可被攻击者控制或接管的插件仓库;
  3. 托管平台允许相应的恶意默认分支名称;
  4. 市场清单或更新流程触发新的 Pin/重新安装;
  5. 客户端自动更新或其他无交互路径执行 Checkout;
  6. 插件生命周期能够执行代码,且沙箱、网络或凭据权限足以产生影响。

缺少其中某些条件,攻击可能降级为需交互安装、无法复现或影响受限。安全文章不应把“研究者完成端到端 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、包依赖、构建脚本与最终发布物。

企业紧急排查与处置步骤

结论:先切断自动更新和不可信来源,再升级、重装、核验与轮换凭据。 仅升级客户端并不能自动证明旧插件从未运行过恶意代码。

  1. 盘点资产: 列出 Claude Code、Codex、Copilot、Gemini CLI 的所有版本、安装方式、IDE 捆绑版本、远程 Worker 与用户范围。
  2. 导出插件清单: 记录插件名、市场、仓库 URL、Pin、安装目录、自动更新状态、首次与最后更新时间。
  3. 立即隔离高风险源: 暂停来自允许 SHA 形状分支或自托管源的自动更新;无法验证的插件先禁用。
  4. 升级客户端: Claude Code 和 Codex 升级到厂商当前稳定版且不低于 AIR 报告的修复点;其他产品向厂商确认。
  5. 检查危险引用: 在内部镜像侧拒绝 40 位十六进制分支名、FETCH_HEAD 等保留名称以及异常默认分支变更。
  6. 重新解析并验证: 对每个 Pin 在隔离环境重新 Fetch,比较最终 HEAD^{commit},生成文件清单和内容摘要。
  7. 全量重装插件: 从清洁镜像和已验证制品重新安装,不直接信任现有缓存与工作树。
  8. 调查主机活动: 搜索异常子进程、网络连接、启动项、SSH/云凭据访问、Shell 历史、插件 Hook 与新增二进制。
  9. 轮换可能暴露的凭据: 以插件执行权限为界,轮换 Git、包仓库、云、MCP、数据库和签名密钥;先吊销后补发。
  10. 恢复与监控: 分批恢复插件,启用安装日志、出网控制、行为检测和 Pin-to-HEAD 持续校验。
Plugin4Shell 企业十步应急闭环,从资产盘点、停止自动更新到升级重装、凭据轮换和持续监控
Plugin4Shell 十步应急闭环:升级只是中间步骤,既有插件与主机还需要完整性核验、调查和凭据轮换。

插件市场与企业治理如何补强

结论:市场审核不能替代客户端验证,但可以降低上游接管与恶意更新概率。 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 校验与灰度,再由客户端自动拉取内容摘要固定的企业制品。

参考来源

内容核验日期:2026 年 09 月 25 日

安装部署教程

环境配置与 Docker 工作流

适合阅读安装部署、本地配置、服务器搭建和自动化流程类文章后继续转化。

环境配置资料包 包含 Windows / Mac / Linux 常见环境配置、依赖安装和报错排查清单。 查看资料包 Docker 工作流包 整理 Docker 部署模板、compose 示例和常用服务编排流程。 查看资料包

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注

本站累计访问量: 387,632 次
AI Stack Nav 客服会员 / 支付 / 下载 / 工具库
你好,我是 AI Stack Nav 客服助手。你可以问我会员开通、微信支付、资料下载、订单入口、AI 工具库等问题。