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