18 万个 MCP server 涌进 GitHub,权限审计该盯哪几处

最近一年,MCP server 的供给量已经多到让审计这件事变得紧迫。直接看 GitHub 公开 API:用 mcp server 做关键词,total_count 返回约 18 万(2026-10-31 抓取时是 180,712;这个总数会随新仓库创建实时浮动,每次查询可能差几十个),incomplete_results 为 false,来自 GitHub Search API。这个数字不代表有 18 万个可用的服务器,但足以说明一个问题:当老板问“这个 MCP server 能不能接”,你如果只看 README 和 tool 列表,基本等于没看。

MCP server 本质上是通过工具接口给模型提供外部能力。客户端看到的是一组工具声明,每个声明通常包含名称、描述和输入结构。这里的关键在于:描述和输入结构都不是权限边界。一个工具可以描述成“从数据库查询订单”,输入结构只有 order_id 一个字符串,但 handler 内部完全可以执行更新、删除,甚至再联网发信息。声明只告诉模型“你可以这么调用我”,不告诉模型“调用之后会发生什么”。所以权限审计的第一性动作,不是看这个服务器有没有漏洞,而是把声明和实现放到同一张表里对照。这也是本文不去做漏洞利用的原因:那一层需要动态环境和具体目标,而声明与实现的不一致,静态就能看出太多。

这次 API 快照里,按 stars 排序的第一条并不是某个 server,而是一份聚合清单——punkpeye/awesome-mcp-servers,97,111 个 star、18,884 个 fork,MIT 许可,最近一次 push 在 2026-10-23(仓库)。这本身就说明问题:你在 GitHub 搜”mcp server”,排最前面的不是可审计的实现,而是一份目录。真正的 server 里,靠前的是 headroomlabs-ai/headroom(78,441 stars)、zylon-ai/private-gpt(57,621)、microsoft/playwright-mcp(38,889)。这些数字都是 GitHub API 返回的官方数据,只能说明仓库活跃度和社区关注度,不能说明每个 server 内部工具声明的准确性。作为对照,awslabs/mcp 这个 Python 写的开源 MCP server 集合目前是 9,854 个 star、1,841 个 fork,Apache-2.0,最近一次 push 在 2026-10-30(仓库)。数字远小于上面的清单,但它至少是一堆可以逐个审的 server 实现。清单省搜索成本,server 集合才谈得上审计。

要批量核对声明和实现,第一步是把待审仓库列表拉下来。下面这个命令直接复现拿到样本的方式,调的是 GitHub 官方 API,按 stars 排序取前 20 条:

curl -s 'https://api.github.com/search/repositories?q=mcp+server&per_page=20&sort=stars' 
| jq '.items[] | {full_name, stargazers_count, language, html_url}'

这里用到的 full_name、stargazers_count、language、html_url 都来自 API 原始 JSON,不涉及 MCP 协议字段。拿到列表只是入口,真正的审计要从默认分支拉代码,逐个定位工具注册点。样本选取也需要注意:sort=stars 会把大厂仓库和聚合清单推到前面,它们不是典型的长尾 MCP server。真正接入风险高的,往往是 stars 不多、更新不频繁、但恰好解决了某个特定业务需求的小仓库。所以更好的做法是同时按 stars 和最近 push 时间两组筛选,避免只看到头部。

拉取代码可以用同一批 API 字段串起来:

curl -s 'https://api.github.com/search/repositories?q=mcp+server&per_page=10&sort=stars' 
| jq -r '.items[].clone_url' | while read url; do
    git clone --depth 1 "$url" "repos/$(basename "$url")"
done

这里只用了 clone_url,同样来自 API 返回结构。克隆之后,审计的核心动作才真正开始:定位 tool 定义和实际处理函数。MCP server 的工具注册方式在社区里有几种常见写法,有的直接调 server.tool(),有的用 @mcp.tool() 装饰器,也有通过 list_tools 统一返回。这些都是社区实现中常见的写法,不是 MCP 官方规范要求。审计时不能只盯着某个装饰器,要把工具入口和后面的 handler 函数连起来看。举一个抽象例子:工具描述写的是从允许目录读取文件,但 handler 里没有任何路径校验,直接 open(path)。这就是声明与实现不一致。具体到单个仓库是否真存在这种偏差,本文没有对全部候选逐一跑完整静态分析,只做了入口核对;但从代码模式看,这类问题在长尾小仓库里并不罕见。

如果只有五分钟,按这五条过一遍,能挡住大部分低级问题:

  1. 拉出所有 tool 的声明:名字、描述、输入类型,确认没有遗漏。
  2. 找到每个 tool 对应的 handler,检查描述里的“只读 / 只写 / 删除”和代码调用是否一致。
  3. 检查环境变量的读取关键字,看有没有未在文档里披露的 key、token、密码。
  4. 检查所有网络请求的 host 白名单,和 tool 描述里承诺的作用域是否一致。
  5. 检查文件路径参数是否经过规范化、是否限制在某个根目录内。

这五条不是官方规范,而是这次静态扫描里常用的工程做法。它们解决的不是“这个服务器有没有漏洞”,而是“你接进来之前,知不知道它到底能碰什么”。

环境变量是声明与实现偏差最集中的地方。很多 server 会在 README 里列出必需的一个 API key,但代码里可能还读了 AWS_SECRET_ACCESS_KEY、GITHUB_TOKEN 这类额外凭据。对使用者来说,这不是小问题。因为你一旦把这个 server 跑在自己的开发机或 CI 里,它读到的就不只是你主动提供的那一个 token。网络出口也一样:一个只承诺访问某个云服务的 MCP server,代码里如果允许任意 requests.post(url),那它的实际可访问范围就远大于 tool 描述。

当然,声明与实现不一致不一定等于漏洞。有些偏差只是写得随意,有的是历史兼容问题,有些是 handler 里包含多个分支但描述只写了主路径。安全社区的一种观点是,动态行为比静态声明更重要,静态审计会漏掉运行时配置和上下文变化。这个观点有道理。但对大多数接入方来说,静态核对是最低成本的第一道闸:如果连声明和代码都对不上,后面动态沙箱做得再细,也无法解决模型被错误描述误导的问题。另一个容易踩的坑,是把 star 数当安全指标。awslabs/mcp 有 9,854 个 star,不代表它内部的每个工具都做过权限边界核对,它只代表这个仓库被很多人看到过。聚合清单更是如此,punkpeye/awesome-mcp-servers 的收录标准是“有没有用”,不是“权限够不够收敛”。

这套方法最适合两类场景:一是团队要新接一个第三方 MCP server,想在跑起来之前看一遍它到底暴露了哪些工具;二是自己发布 server 前做一次自查,避免描述写得比实际能力大。它不适合做漏洞利用的完整分析,也不适合替代运行时沙箱。动态行为审计仍然需要单独做,比如沙箱里只给一个临时目录和一个白名单网络出口。但动态沙箱的前提,往往还是你先通过静态声明摸清了它想要什么。

MCP server 的数量还会继续涨。18 万个仓库这个数,几个月后可能过 20 万——这是按当前增速做的个人推测,不是 API 数据。对普通开发团队来说,没必要去审计整个生态,但必须在接入前把“声明了什么”和“实际能做什么”对齐一次。工具清单、环境变量、网络出口、文件边界,这四组信息只要放进同一张表,很多问题会自己浮出来。剩下的,就交给运行时沙箱和更新机制来处理。

参考来源:

评论区

0 条评论

登录后可评论。

青柠气泡 15 阅读