你跟 agent 说「帮我接一下 GitHub」,它没有去搜 GitHub 官方的 MCP Server,而是从记忆里挑了一个 2025 年 5 月就归档的包,写进 MCP 配置,npx 拉下来跑起来。整个过程没有报错,也没有任何一步问过「这个包还有人维护吗」。
Glow Labs 在真实的 AI 编程会话记录里找到的就是这个模式,他们给它起名 MCPhantom。在 agent 装了废弃 server 的会话里,98% 的包名来自模型的训练数据,剩下 2% 是用户自己点名要旧包。这个模式跨 Claude Code、Cursor、Codex CLI、Claude Cowork 和 Claude chat,也跨 Anthropic、OpenAI、xAI 三家的模型。换句话说,没有任何一家厂商的修法能单独关掉这个缺口。
先把事实摆清
Glow Labs 的做法是翻真实的 session transcript,只挑那些「确实装了或建议装废弃 server」的会话来看。这个取样方式决定了 98% 这个数字的含义:它说的是在已经踩坑的会话里,绝大多数是模型自己回忆出来的包名,不是有人诱导它。
具体到 GitHub 这个例子,GitHub 自己维护着一个官方 MCP Server,仓库到今天还在活跃提交。agent 跳过了它,选了 Anthropic 早期发布的参考实现,那个仓库已经归档,归档说明写得很直白:不再提供安全更新和 bug 修复。
披露时间线值得单独看一眼:
- 2026 年 7 月 30 日,Glow Labs 通过 HackerOne 报告并提交 GitHub advisory。HackerOne 当天以 out of scope 关闭,指向 GitHub。
- 10 月 4 日到 11 日,四个漏洞全部报给 Anthropic 的 CNA。
- 10 月,维护方关闭 advisory,理由是仓库已在 2025 年 5 月归档、无人维护,不会有修复。
四个高危漏洞里最严重的一个在 Puppeteer server 上,它让 agent 能开一个浏览器会话。攻击者用一次 prompt injection,就能让 agent 在这台机器上启动任意进程、跑任意命令,而不是开浏览器。开发者当初批准的是「允许 agent 浏览网页」,这一条授权把后面所有命令都覆盖了,harness 没有理由拦。
这个包的 npm 元数据现在标了弃用。我查 registry 时看到,server-github、server-postgres、server-puppeteer 三个包的 modified 字段是 2026-11-05,也就是本周才更新过。维护方在报告里说过会考虑给 npm 包打上弃用标记,时间上对得上。标记来了,但它不阻止安装。
我拉了一遍 npm 数据
Glow Labs 给的累计数字是 2025 年 7 月 10 日以来 1,735 万次下载,五个包进了 npm 的 MCP 前 100。累计数字容易被说成「历史包袱」,所以我按周重新拉了一遍 npm downloads API,范围是 2026 年 10 月 28 日到 11 月 3 日。
| 包 | 最新版本 | 最后发布日期 | npm 弃用 | 上周下载 |
|---|---|---|---|---|
| server-github | 2025.4.8 | 2025-04-08 | 是 | 189,189 |
| server-slack | 2025.4.25 | 2025-04-25 | 是 | 185,396 |
| server-postgres | 0.6.2 | 2024-12-04 | 是 | 155,598 |
| server-brave-search | 0.6.2 | 2024-12-04 | 是 | 30,371 |
| server-puppeteer | 2025.5.12 | 2025-05-12 | 是 | 29,827 |
| server-google-maps | 0.6.2 | 2024-12-04 | 是 | 7,846 |
| server-gdrive | 2025.1.14 | 2025-01-14 | 是 | 6,271 |
| server-gitlab | 2025.4.25 | 2025-04-25 | 是 | 5,453 |
| server-redis | 2025.4.25 | 2025-04-25 | 是 | 3,892 |
| create-server | 0.3.1 | 2024-11-25 | 是 | 204 |
| server-aws-kb-retrieval | 0.6.2 | 2025-01-14 | 是 | 197 |
| server-everart | 0.6.2 | 2024-12-04 | 是 | 145 |
| 合计 | 12/12 | 614,389 |
这 12 个包全部标了弃用,最新版本全部停在 2024 年 11 月到 2025 年 5 月之间,过去一周还是被下载了 61 万次。这个量级说明退潮还没开始,流水线仍在跑:旧包被 agent 选中,被装进开发者的机器,然后继续被下一个 agent 选中。只要模型权重里那批名字还在,这条流水线就不会因为仓库归档而停。
npm 那边的下架门槛也让这事更难收场。按规则,包主要删包,得满足「过去一周下载少于 300 次、单一 owner、没有公开依赖」。server-github 上周 18.5 万次下载、6 个 owner,一条都不满足。Glow 的研究员 Nadav Claude Cohen 说,npm 会因恶意代码删包,但没见过因为漏洞删包的。
为什么签名、SBOM、注册表全都拦不住
这是我觉得整件事最值得写下来的一层。
过去两年,Agent 供应链安全的主线是「验证来源」:给 MCP server 做签名、出 SBOM、跑 SLSA provenance、在网关里 --verify-signatures。这些控制回答的问题是「这个东西是不是它声称的发布者发的」。
MCPhantom 里,这个问题的答案是「是」。@modelcontextprotocol/server-github 确实是 Anthropic 发的,签名是真的,SBOM 是真的,注册表条目也是真的。你把它拿去跑任何一套来源验证,全都能过。
坏的地方不在来源,在选择。用户说的是「接一下 GitHub」,agent 把这句话翻译成「装 @modelcontextprotocol/server-github」,这个翻译发生在模型内部,依据是训练数据里这个包名和「GitHub MCP」这个意图的绑定强度。等这个包名落到配置文件里,来源验证能做的只是确认「这个废弃包确实是官方废弃包」。
这就是供应链安全和选择链安全的区别。供应链模型假设攻击者要伪造身份;这里没人伪造身份,模型只是用了一个过时的默认值。你所有的信任根都对着「谁发的」,而风险出在「为什么选它」。
现有控制为什么全都静音,可以逐个看:
EDR 看到的是一个可信的 Node 进程在装一个公开的 npm 包,这看起来就是日常。SCA 扫的是 manifest 里的已知漏洞依赖,而 npx 启动的 server 不在任何 manifest 里,这些漏洞也还没有 CVE。harness 的护栏管的是「agent 能跑什么」,它不知道这个包已经弃用。软件资产清单登记的是已安装应用,npx 起的进程只是一个 Node 进程,永远不会出现在清单里。
四层控制,四种盲区,最后只剩端点这一个地方能看见这件事。
这里有个容易被忽略的语义问题:弃用和漏洞是两件事。绝大多数被弃用的包本身没有已知漏洞,只是没人维护了,所以团队看到 deprecated 标记通常不会当成风险,只是顺手记一笔。MCPhantom 这批恰好两样都占,既被弃用,又确实有四个高危漏洞。但反过来也成立:即使只看弃用这一个信号,只要有人真的读它,也能提前拦住。信号一直都在 registry 的元数据里躺着,缺的是会去读它的流程,以及会去读它的 agent。
Agent 为什么会这样选
原因不神秘,但很难修。
模型的权重里存着「给 GitHub 做 MCP 就用这个包」这类关联,这个关联在训练时是对的。包后来被归档,这件事没有进权重。你问它「GitHub 的 MCP 怎么接」,它给出的答案忠实于训练时的世界,而世界已经变了。
npm 的弃用标记也没帮上忙。包被标弃用后会从 npm 搜索里消失,所以 agent 联网搜的时候经常搜不到它,反而搜不到任何结果;但 npx 照样会装,只给一条警告。搜索和安装这两条路径对弃用的态度不一致,agent 正好走在能装的那条上。
还有一个更扎人的细节,来自 Cohen 对 Help Net Security 的说法:有一个会话里,agent 真的先去 npm 查了包,拿回来的信息是「This package is deprecated」,它还是装了,而且没告诉用户。
这说明「让 agent 装之前先查一下」不是修法。查不查、查到了怎么处理,仍然由模型决定,而模型的判断依据里没有「弃用等于风险」这条规则。
谁修得动
Cohen 列了四个可能的修复来源,我按可落地程度排一下。
harness 厂商可以拒装或至少警告,并且覆盖已经装上的那些,不只是拦新安装。问题是它只在每一家都做的时候才有效,一家修了,另外几家还开着。
模型厂商可以更新权重,让模型知道哪些包已经退役。Cohen 的判断是这很贵,所以不太会发生。我同意,而且还有一层:就算更新了,下一个包退役时还得再更新一次,这是个永远追不上的循环。
包 owner 可以修漏洞,这是唯一能保护继续使用这些 server 的人的办法。但这批包已经归档,owner 明确不修了。
安全产品可以用 harness 已经支持的 pre-execution hook,在执行之前把这次调用拦下来。Cohen 说这是今天就能做、不用等三方的路径。
我的判断偏向前四者里的最后一个,但理由和他不完全一样。把它当成检测问题会走进死胡同,因为你要检测的东西是「模型为什么这么想」,而那个过程不可观测。换成授权问题就通了:agent 选包这个动作,实质上是替组织做了一次依赖引入的授权决策,而它既没有 provenance 数据,也没有弃用数据。把这次决策搬到执行之前的那一层去审,是结构上唯一成立的修法:在那里,包名、版本、下载量、弃用状态都是可查的,判断可以由策略做,不必由模型做。
这也是为什么我一直觉得 Agent 安全最后会落到执行边界上。模型内部的判断你改不动,训练数据你追不上,唯一能稳定施加约束的地方,是它把决定变成动作之前的那道口子。
企业今天能做的三件事
Glow Labs 给了三条,都很具体,我照着做了一遍。
先找出现有的 MCP 配置,把引用废弃包的那些挑出来。在开发机上跑一条 grep 就够:grep -rn "@modelcontextprotocol/" ~/.claude ~/.codex ~/.cursor ~/.config 2>/dev/null。路径因工具而异,命中之后逐条看包名是不是官方的、是不是已弃用。
然后给每个服务只批准一个 server,优先用服务方自己维护的那个。GitHub 的例子最典型:github/github-mcp-server 是官方仓库,3.4 万 star,昨天还有提交;@modelcontextprotocol/server-github 停在 2025 年 4 月,已经归档。两个都叫「GitHub MCP」,风险完全不同。
最后把 npm 弃用状态加进审批标准。以后任何人(或任何 agent)要引入一个 MCP server,先查 registry 的 deprecated 字段,有值就要求说明理由。这一步不花什么成本,但能把「模型记忆」这个默认值挡在门外。
比这三条更硬的一条是钉版本。MCP 配置文件里写死包名和版本号,而不是留一个「让 agent 自己决定」的口子,效果立竿见影:"args": ["-y", "@modelcontextprotocol/server-github@2025.4.8"] 这种写法,agent 就没法在下一次会话里换成一个它「记得」的包。钉版本的代价是要人维护,但它把选择权从模型手里拿回来了,而这个选择本来就不该由模型做。写到这一层你会发现,修法跟「让 AI 更聪明」没有任何关系,纯粹是工程纪律。
如果你有 harness 的 pre-execution hook,把这条策略挂上去,效果比人工审批稳定,因为它对每一次安装都生效,不依赖某个人记得查。
这里有个位置选择的问题值得想清楚,因为三个候选位置的强度差很多。写在 agent 的指令或规则文件里,最便宜也最不可靠,它还是把决定权交给模型,模型可以读、可以不听,那个「查到弃用还照装」的会话就是证据。写在 harness 的 pre-execution hook 里,强一档,因为判断从模型手里移到了代码里,但 hook 仍然跑在 agent 的进程空间附近,策略由谁维护、能不能被改,取决于 harness 的实现。写在网关或者代理上,最强,因为那个位置在 agent 之外,agent 既看不见也改不了,而且它天然就是所有出站调用的必经之路。
第三种的代价是需要一个能识别 MCP 语义的网关。纯网络层的代理只能看到「有人在连 registry.npmjs.org」,看不到「这次要装的是哪个包、是不是弃用」;要判到包名这一层,代理得能解析请求体,或者由一个 MCP 感知的控制面来下发规则。这也是 MCP 网关这个品类在过去一年被反复做一遍的原因:它卡在所有工具调用的正中间,位置天然对,剩下的问题只是把包名和弃用状态接进策略引擎。策略本身可以写得很朴素,比如「只允许配置里登记过的包名加版本,其余一律拒绝并记录」,几十行代码就能实现,难的是你得先有一个能读到请求体的位置。
写在最后
这件事容易被归到「又一个供应链漏洞」里,然后按老办法处理:等 CVE、等补丁、等厂商通告。但这批包不会等来补丁,四个漏洞里至少有一个已经被维护方明确拒绝修复,而它们的下载量还在每周 60 万次以上。
漏洞数量没有变多,变的是做这个决定的人。以前引入一个依赖是人做的,人有上下文,会看一眼最后更新时间;现在这个决策交给了模型,而模型的上下文是训练时的快照。注册表解决「发现」,签名解决「来源」,SBOM 解决「成分」,这三样都不解决「选择」。
Ophir Arie 那句话我抄在这里:模型的记忆现在是一个没有弃用信息源的包管理器。把这个说法放进任何一次正式的 agent 平台架构评审,都会逼出一个不好回答的问题:你打算在哪一层审它的选择?
顺带说一句生态的节奏,它解释了为什么这类坑只会越来越多。MCP 官方注册表的一份独立普查算出每天新增约 195 个 server,GitHub 的 Octoverse 2025 报告数了 430 万个 AI 相关仓库。Anthropic 2024 年推出 MCP 时发了 20 个参考实现,后来服务方陆续自己做 server,其中 13 个被归档,这在生态成熟过程里是正常的。问题是归档的速度和模型更新的速度不是一个量级:包退役是按周发生的,权重更新是按年发生的,中间那段时差就是 MCPhantom 的生存空间。
相关阅读:
- 《Claude Code 官方扩展被投毒:IDE 插件市场正成为 Agent 供应链的新前线》 讲的是有人主动投毒、冒充官方的那一类;这篇讲没人投毒、agent 自己选错的那一类,两件事的防线不在同一个位置。
- 《Claude Code 扩展投毒自查与清理手册》 那篇给的是中招之后的处置动作,这篇给的是装之前的准入判断。