MCP 供应链体检:15,341 个服务器扫描与上线前 8 项检查

15,341 个 MCP 服务器里 64.8% 能联网、21.1% 能开 shell、7.4% 装的时候就跑代码。附 npx 之前必须做的 8 项检查。

分享
MCP 供应链体检:15,341 个服务器扫描与上线前 8 项检查

一句话结论:给 Agent 加一个 MCP 服务器,等于把那个服务器能做的事原样交给它——包括联网、读文件、开 shell、在安装时执行代码。2026 年 10 月,两件事把这个问题摆到了台面上:一个假冒 REA 的仓库用复制来的 README 分发带木马的 Windows 程序;同一周,一份对官方 MCP 注册表 15,341 个组件的静态扫描给出了整体画像——64.8% 能发起网络请求,25.0% 能读文件系统,21.1% 能执行 shell,7.4% 在安装时就运行代码。本文把这两份材料合成一套可执行的验收流程:先看群体数据,再走 8 项上线前检查。

披露:扫描报告作者是 SkillTotal(Apache-2.0 开源扫描器)的开发者,方法与聚合数据公开、可复现,文中数字以其公开报告为准;8 项检查的作者是 Hermes IDE 的开发者,检查方法本身与具体产品无关,可在任意终端复现。两篇源文都发布于 2026-10-16。


一、先看事故:假 REA 仓库是怎么被发现的

2026-10-13,REA(一个让 coding agent 逆向二进制、Electron 应用、APK 的开源 MCP 服务器与 CLI)的仓库收到 issue #2424,标题很直接:「一个假仓库 rea-tools/rea 在分发带木马的 Windows exe」。

按该 issue 的描述:

项 细节
手法 复制 REA 的 README,加一节「Windows Standalone .exe」,指向一个 82.6 MB 压缩包
检测 Microsoft Defender 把压缩包标为 Trojan:Script/Wacatac.B!ml,解压出的 rea-x64.exe 标为 Trojan:Win32/Wacatac.B!ml
多引擎 VirusTotal 上 8/45 个引擎报毒
传播量 172 次下载
仓库背景 rea-tools 组织创建于 2026-10-10,名下只有这一个仓库;名字在模仿 REA 的真实站点 rea.tools
处置 维护者当天关闭 issue,并通过 PR #2429 更新 README:只从 morluto/rea、rea.tools 或 npm 包 rea-agents 获取

REA 当时的热度正是攻击者选择的理由:走红几天内约 14.5 万 star,目前仓库已超过 28 万。README 现在多了一句关键的话——「相似的仓库名或复制的 README,不能证明下载是官方的。」

这句话就是 MCP 生态的核心问题:你从一条推文或一个列表里看到服务器名,把一行配置粘进 agent,agent 就用你的权限把它跑起来。

二、群体画像:15,341 个服务器能做什么

扫描对象是官方注册表 registry.modelcontextprotocol.io/v0/servers。它按"发布版本"计数,所以原始条目会高估生态规模:2026-08-16 快照的 73,460 条记录,按来源去重后是 17,535 个组件。其中 15,341 个(87.5%)完成扫描,其余 2,194 个按原因计数:

未扫描原因 组件数 占比
仓库或包已不可访问 1,637 9.3%
超过 60 秒时限 302 1.7%
超过 50 MB 体积上限 188 1.1%
其他 65 0.4%
访问被拒绝 2 0.0%

按托管位置看,覆盖率差异很大:

生态 已扫描 已列出 覆盖率
npm 6,967 7,152 97.4%
PyPI 2,985 3,071 97.2%
git 5,389 7,312 73.7%

近十分之一(9.3%)的组件指向一个已经消失或私有的仓库/包。 这本身就是一条供应链事实:你抄下来的 npx 行,可能指向一个已经不由原作者控制的东西。

方法是确定性的静态分析:正则规则 + Python AST,代码只下载阅读、从不执行,也不调用 LLM。同一份代码、同一个引擎、同一个 Python 版本,结果可复现。运行参数:SkillTotal 0.49.0、规则集 56–59、单组件 50 MB / 60 秒上限,读取每个组件在运行时的最新 release 或默认分支。

能力表:不是漏洞,是"Agent 会继承的触达面"

能力 组件数 占 15,341
暴露 MCP 工具 13,524 88.2%
能访问网络 9,938 64.8%
读文件系统 3,828 25.0%
执行 shell 命令 3,230 21.1%
写文件系统 3,206 20.9%
安装时运行代码 1,134 7.4%
使用委托式(OAuth/OIDC)认证 895 5.8%
动态求值代码 475 3.1%
使用受限、短时效身份 81 0.5%

几个必须记住的读法:

  1. 三分之二的服务器能对外发请求。 如果你担心数据外流,默认假设"它发得出去"。
  2. 五分之一能开 shell,五分之一能写盘。 对构建工具、文件管理器、开发环境服务器来说这很合理;但这也正是把提示注入升级成代码执行或持久化的两个条件。审一个服务器时,这两个数字应该出现在审批单上。
  3. 1,134 个(7.4%)在安装时跑代码:npm 的 install/prepare 脚本,或 Python 安装钩子。这段代码在你调用任何工具、阅读任何工具描述之前就已经运行。
  4. 只有 81 个(0.5%)呈现"受限、短时效身份"模式,而 895 个(5.8%)使用委托式 OAuth/OIDC。两个数字都只覆盖规则能识别的写法。

风险分级的读法:99.3% low 不等于安全

风险等级 组件数 占比
low 15,225 99.3%
medium 90 0.6%
high 22 0.1%
critical 4 <0.1%

在这套规则集下,没有任何组件命中"攻击形状"规则。作者自己给了三条限制:规则先在这批人群上调过;静态扫描看不到运行时才拉取的代码;也看不到还没有规则描述的攻击。而且更新的规则集(当前为 76)会读 MCP 启动命令和嵌套工具 schema 字段,所以"零命中"只对这一轮成立,不是"某个组件安全"的结论。

按 OWASP Agentic Skills Top 10 归类,出现最多的是:

  • AST03 过度授权技能:1,905 个(12.4%)
  • AST02 供应链劫持:1,216 个(7.9%)

git 侧还呈现集中度:7,312 个 GitHub 组件分布在 4,325 个 owner 手上,单个 owner 占 18.0%,前十大 owner 占 29.1%。

三、上线前 8 项检查(以 REA 为例)

第 1、2 项就能挡住 rea-tools 这个复制品;其余 6 项覆盖它没走、但下一个可能会走的路径——你配置里 npx 拉下来的那个 npm 包。

1. 名字从项目本身取,不从搜索结果取

从项目自己的 README 或官网取包名与仓库,然后与配置里的字符串逐字符比对。REA 的 README 只列三个来源:morluto/rea、rea.tools、npm 包 rea-agents。其他都不算,包括 README 一模一样的仓库。仿冒品通常只差一个字符:用域名当组织名、一个连字符、一个复数。

2. 查仓库归属,以及它是什么时候建的

GitHub REST API 会返回仓库创建时间、是否为 fork、以及父仓库。用 gh api --jq 一次打印:

gh api repos/morluto/rea \
  --jq '.created_at, .fork, .parent.full_name'

如果 owner 是组织,也查组织:

gh api orgs/<org> --jq '.created_at, .public_repos'

一个三天前创建、名下只有一个仓库的组织,却在分发一个六位数 star 的工具——这就是 REA issue 里描述的形状。复制品通常以全新仓库推送,fork: false,所以日期比 fork 标志更有信息量。

3. 确认 npm 指回同一个仓库

npm view 只读注册表元数据,不安装,一次取多个字段:

npm view rea-agents repository.url maintainers time.created

rea-agents 的注册表记录指向 git+https://github.com/morluto/rea.git,维护者账号与仓库 owner 一致——这是你要的匹配。但 repository 是发布者自己写进 package.json 的字段,谁都能声称自己对应任何仓库,单独看它证明不了什么。用第 4 项兜底。

4. 查 provenance,再验签名

npm provenance 公开声明"这个包在哪里构建、由谁发布"。rea-agents@6.5.0(2026-10-16 的 latest)带有 provenance 证明(https://slsa.dev/provenance/v1),由 GitHub Actions 通过 trusted publishing 发布。直接读证明类型:

npm view rea-agents@6.5.0 \
  dist.attestations.provenance.predicateType

要验证包和它拉进来的所有依赖,先装进一个临时目录并禁止运行脚本,再验签:

mkdir mcp-check && cd mcp-check
npm init -y
npm install --ignore-scripts rea-agents@6.5.0
npm audit signatures

npm 的输出会给出"签名已验证"和"证明已验证"的包数量。它的边界也写在文档里:provenance 不保证包里没有恶意代码,它只告诉你 tarball 是哪个仓库、哪条工作流构建的。代码安不安全,仍然要你判断。

5. 在安装脚本运行之前先读它

npm 文档列出的 preinstall、install、postinstall、prepare 会在 npm install -g <pkg> 时运行(prepare 仅对 git 和本地目录安装)。带 binding.gyp 又没有 install/preinstall 的包,会把 node-gyp rebuild 当作安装步骤。一次问清:

npm view rea-agents@6.5.0 scripts.preinstall \
  scripts.install scripts.postinstall scripts.prepare

REA 6.5.0 没有 preinstall/install/postinstall;它有 "prepare": "node scripts/prepare.mjs",但按 npm 文档,按包名从注册表安装时 prepare 不会运行。所以从 npm 装,安装期什么都不跑。如果你查的服务器确实有安装钩子,去读它指向的那个文件——第 8 项教你怎么拿到它。

6. 搞清你的 npm 会怎么处理脚本

先跑 npm --version,因为规则今年变了。npm 12 默认跳过依赖的安装脚本,除非你显式允许。对 npx 和全局安装(没有项目 package.json 可放 allowlist),npm 12 文档要求传 --allow-scripts=<pkg>,或用 npm config set allow-scripts=<pkg> --location=user 持久化。

npm 11 及更早版本默认运行脚本,所以要在 MCP 配置的 npx 参数里加 --ignore-scripts。npm 文档说明它会覆盖 allow-scripts,因此在 12 上也无害。如果某个服务器离开安装步骤就坏,那它需要安装期代码——读完那段代码再决定。

7. 锁定确切版本,不要用 @latest

REA 的 README 用 npx -y rea-agents@latest 做一次性 CLI 运行,很多 MCP 安装片段也这么写。但在 MCP 配置里,这一行每次 agent 启动都会执行。latest 是移动标签:下一个版本由谁发布,它就指向谁;-y 跳过 npx 的确认提示。

把检查过的版本钉死:rea-agents@6.5.0。等你要升级那天,npm 的 min-release-age 只安装"已经发布超过 N 天"的版本:

# ~/.npmrc
min-release-age=7

一周时间足够让一个被劫持的 release 先被举报,再到达你的 agent。

8. 把 tarball 和源码做 diff

你的 agent 运行的是注册表上的 tarball,不是你在 GitHub 上读的仓库。用 npm pack 拉下来、解压、和 release tag 对比:

npm pack rea-agents@6.5.0
tar -xzf rea-agents-6.5.0.tgz
git clone https://github.com/morluto/rea
git -C rea checkout <release-tag-or-commit>
diff -rq package rea | grep -v '^Only in rea'

checkout 到 release tag,或 provenance 证明里写的那个 commit。过滤掉只在仓库里存在的文件(比如测试),剩下的两类行都要读:只在 tarball 里的文件,以及与 tag 不一致的文件。未提交的构建产物会出现,这是预期;其余每一项都要看,从 bin 和生命周期脚本指向的文件开始——这些文件没有人在 GitHub 上审过。

四、锁定版本要落到每个 agent 的配置里

每个 agent CLI 有自己的 MCP 配置,在一个文件里钉版本,对下一个 agent 无效:

Agent 项目文件 用户文件
Claude Code .mcp.json ~/.claude.json
Codex .codex/config.toml ~/.codex/config.toml
Antigravity CLI .agents/mcp_config.json ~/.gemini/config/mcp_config.json
Copilot CLI 见官方文档 ~/.copilot/mcp-config.json
OpenCode opencode.json(mcp 键) 见官方文档

Claude Code 与 Codex 的钉版本写法(包名占位):

claude mcp add --scope project your-server \
  -- npx --ignore-scripts -y your-mcp-server@1.4.2
[mcp_servers.your-server]
command = "npx"
args = ["--ignore-scripts", "-y", "your-mcp-server@1.4.2"]

OpenCode 把同样的参数放进 "type": "local" 下的 command 数组。

把 .mcp.json 当代码审。 Claude Code 在交互会话里会先问是否使用项目级服务器,但在 claude -p、Agent SDK 和云运行里,按官方文档它"不询问就加载项目级服务器"。如果你的 CI 在 PR 分支上跑 claude -p,那么 PR 里改一行这个文件,下一次任务启动就会执行它。

五、把两件事合成一个卡点

群体数据回答"这类东西平均能做什么",8 项检查回答"这一个能不能进我的配置"。落到流程上,建议在采购/引入环节加一张表:

检查 命令/证据 通过标准
名称来源 项目 README / 官网 与配置逐字符一致
仓库年龄 gh api repos/<o>/<r> --jq '.created_at,.fork' 不是新近创建的单仓库组织
npm 回指 npm view <pkg> repository.url 与仓库一致(仅作辅助)
provenance npm view <pkg>@<ver> dist.attestations... 有证明且发布者可信
安装脚本 npm view <pkg>@<ver> scripts.* 无脚本,或脚本已读且可解释
版本 配置文件 钉死 @x.y.z,不用 latest
tarball npm pack + diff -rq 差异项全部读过
能力清单 扫描/人工阅读 联网、shell、写盘、读取范围有明确理由

三个典型场景的侧重点不同:

  • 企业采购:能力清单 + 数据流向 + 身份模型(是否有短时效身份)优先;没有审计日志的服务器不进生产。
  • 个人 npx 尝鲜:至少做第 1、2、7 项,并把 --ignore-scripts 写进配置;不要用 @latest。
  • CI / 自动化:把 .mcp.json 纳入 code review,限制 claude -p 在不可信分支上的运行权限。

六、边界:这份体检不能证明什么

  • 静态扫描看不到运行时拉取的代码。 服务器可以在启动后再下载并执行逻辑。
  • "零命中"只对这一轮规则集成立。 规则在人群上调过,且更新的规则集会读更多字段。
  • 能力不是漏洞。 一个能开 shell 的服务器可能就是为了开 shell;数字描述的是"Agent 接手后会继承的触达面"。
  • 扫描器作者自建了 125 个攻击样本 + 89 个良性样本的基准,在规则集 76 上全部命中、无误报;但这只能防回归,不能证明它能识别语料之外的攻击。
  • 单次测量有时点。 组件会更新,重跑会得到不同数字。

七、给团队的三条落地建议

  1. 把"能联网/能开 shell/能写盘/安装期执行"作为审批字段,而不是让每个工程师凭感觉判断。
  2. 把版本钉死与 --ignore-scripts 变成默认模板,在 MCP 配置生成器里写死,减少手工失误。
  3. 给 Agent 的 MCP 配置加一道 diff 门:配置变更必须出现在 PR diff 里,用户级配置(~/.claude.json、~/.codex/config.toml)单独抽查,因为它们不在仓库里、不会出现在 diff 中。

如果你正在给团队搭 Agent 工具链,这两份材料值得放在同一页:一张告诉你风险面有多大,一张告诉你单个服务器怎么过关。前者用于定策略,后者用于做验收。


Sources

相关阅读:Agent / MCP / 自动化完全指南 | Agent 安全九起事故与六项控制 | 192 次运行实测:MCP 工具数量的真实成本

本文由汇智网编译整理,转载请注明出处。文中数字均来自上述 2026-10-16 的公开材料;扫描器与 IDE 作者的商业立场已在文首披露。

这是「Agent / MCP」专题的一部分。完整地图见:Agent / MCP / 自动化完全指南