> ## Content Index
> Fetch the complete content index at: https://www.hubwiz.com/blog/llms.txt
> Use this file to discover other available public pages before exploring further.

# MCP 供应链体检：15,341 个服务器扫描与上线前 8 项检查
- URL: https://www.hubwiz.com/blog/mcp-registry-supply-chain-audit/
- Published: 2026-10-17T12:42:39.000Z
- Updated: 2026-10-17T13:01:40.000Z
- Description: 15,341 个 MCP 服务器里 64.8% 能联网、21.1% 能开 shell、7.4% 装的时候就跑代码。附 npx 之前必须做的 8 项检查。
- Author: admin
- Tags: MCP, Agent 安全, 供应链, 工具审计

**一句话结论**：给 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` 一次打印：

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

```

如果 owner 是组织，也查组织：

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

```

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

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

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

```bash
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 发布。直接读证明类型：

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

```

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

```bash
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` 当作安装步骤。一次问清：

```bash
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 天"的版本：

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

```

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

### 8\. 把 tarball 和源码做 diff

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

```bash
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 的钉版本写法（包名占位）：

```bash
claude mcp add --scope project your-server \
  -- npx --ignore-scripts -y your-mcp-server@1.4.2

```

```toml
[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**

- SkillTotal, *What 15,341 MCP servers can do: a static scan of the official registry*, dev.to, 2026-10-16：[https://dev.to/dmitrii\_p\_762d68551e7b37f/what-15341-mcp-servers-can-do-a-static-scan-of-the-official-registry-1a93](https://dev.to/dmitrii%5Fp%5F762d68551e7b37f/what-15341-mcp-servers-can-do-a-static-scan-of-the-official-registry-1a93?ref=hubwiz.com)（报告与聚合数据：[https://github.com/pezhik/skilltotal](https://github.com/pezhik/skilltotal?ref=hubwiz.com)）
- Gabriel Anhaia, *Before You npx an MCP Server: 8 Checks That Would Have Caught the Fake REA Repo*, dev.to, 2026-10-16：[https://dev.to/gabrielanhaia/before-you-npx-an-mcp-server-8-checks-that-would-have-caught-the-fake-rea-repo-300o](https://dev.to/gabrielanhaia/before-you-npx-an-mcp-server-8-checks-that-would-have-caught-the-fake-rea-repo-300o?ref=hubwiz.com)
- REA issue #2424 / PR #2429：[https://github.com/morluto/rea/issues/2424](https://github.com/morluto/rea/issues/2424?ref=hubwiz.com)

**相关阅读**：[Agent / MCP / 自动化完全指南](https://www.hubwiz.com/blog/agent-stack/) ｜ [Agent 安全九起事故与六项控制](https://www.hubwiz.com/blog/agent-security-nine-incidents-six-controls/) ｜ [192 次运行实测：MCP 工具数量的真实成本](https://www.hubwiz.com/blog/mcp-tool-count-1932-runs-measured/)

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

> **这是「Agent / MCP」专题的一部分。**完整地图见：[Agent / MCP / 自动化完全指南](https://www.hubwiz.com/blog/agent-stack/)