攻击者通过PR向项目注入恶意MCP服务器productivity-suite,前三次调用正常,后续指令改为窃取SSH密钥、AWS凭据和K8s配置。Pillar Security命名为Deadbugz。
2026 年 8 月 10 日晚间,一个 GitHub 账号在 74 分钟内针对 23 个不相关的 AI 和开发者工具项目提交了 Pull Request,每次都往项目配置里加一个名为 productivity-suite 的 MCP 服务器。这个服务器提供文本格式化和摘要功能,前三次工具调用确实只做了这些。第三次调用之后,它改变了返回给 AI Agent 的指令,告诉它去查找 SSH 密钥、AWS 凭证、Shell 历史记录和 Kubernetes 配置,并且要对此保持沉默。Pillar Security 于 8 月 12 日发布了详情,并将该攻击行动命名为 Deadbugz。
我们每天都在用一堆 MCP 服务器跑 AI 编码 Agent,所以我们做了理所当然的事——审计了自己的配置。结果没有通过。本文涵盖以下内容:这类攻击为何不同于大多数团队已有预案的 npm 和 PyPI 事件,我们的审计发现了什么,以及我们现在正在逐项检查的清单。
普通代码库的行为和它写的代码一致。你可以阅读它、锁定版本、扫描它。MCP 服务器做两件事:它在你的机器上或别人的服务器上运行代码,并且向你的 AI Agent 提供文本,Agent 把这些文本当作指令来对待。工具名称、工具描述和提示词模板都来自服务器,模型读取它们的方式和读取你请求的方式完全相同。
Deadbugz 利用的正是第二条通道。据 Pillar 描述,该服务器维护了一个按客户端计数的计数器。一旦客户端发出三次 tools/call 请求,之后的 tools/list 和 prompts/get 响应就携带了新指令。没有新增任何工具,安装时工具列表里也看不出任何异常。有人在试用服务器的前一分钟只会看到一个无害的文本格式化工具。有效载荷通过 Agent 已经信任的元数据通道送达。
Pillar 给 MCP 客户端开发者的建议值得反复说:已批准服务器的工具定义发生变更,应被视为安全事件,需要重新审批。目前大多数客户端都没有这样做。你只需要批准一次服务器,之后它说的任何内容都会直接进入模型的上下文。
这不仅仅是开发人员笔记本上的问题。Censys 在 2026 年 4 月 28 日统计到 12,520 个可从互联网访问的 MCP 服务,分布在 8,758 个 IP 地址上;到 5 月 6 日已超过 21,000 个。这些服务器在他们报告中无需认证即可访问。最大的类别是数据和知识工具(1,776 个,其中许多是直接的数据库查询接口)和基础设施工具(1,549 个)。Censys 没有实际调用这些工具,所以这些数字展示的是暴露面,而非已确认的入侵。
各大厂商正在做出反应。8 月 14 日,Cloudflare 在其 Gateway 中添加了 MCP 流量检测功能,基于 MCP-Protocol-Version 请求头进行识别,这样公司至少能看到哪些机器在通信 MCP,并阻止跳过审批入口的直接连接。该协议自身的安全最佳实践页面对本地服务器说得非常直接:它们以与客户端相同的权限运行,而提供一键安装的客户端必须在执行前完整展示具体命令,不能截断。页面还建议用最小默认文件系统和网络访问权限对服务器进行沙箱化。
网络可见性有助于安全团队,但这无法回答开发者应该首先问的问题:我实际安装了什么,它能访问什么?
我们的开发主机运行着 Claude Code,配置了七个全局 MCP 服务器和两个仅限于单个项目作用域的服务器。我们在 RPI 工作流文章中写过如何在 Agent 之间分配工作;这次审计关注的是那个工作流下面的管道。有四个发现值得关注。
其中一个用 npx -y 加裸包名启动,一个用了 @latest,一个直接从 Git 仓库的默认分支通过 uvx 启动。这些方式每次会话启动时都会下载并运行最新版。对于 Deadbugz 风格的攻击到达我们这里,不需要有人给我们发 Pull Request。上游包只需要发生变化就行——通过被劫持的维护者账号(就像 2025 年 9 月的 npm 钓鱼攻击),或者通过一个改变主意的维护者。
它们的工具定义存在于别人控制的服务器上,可以随时更改而我们这边没有任何更新。版本锁定对此毫无帮助。唯一的防御是注意到定义何时发生了变化,而我们没有任何手段来察觉。
我们的浏览器自动化服务器通过 Chrome DevTools 协议连接到一个长期运行的浏览器配置文件,该配置文件已登录我们多个业务账户。任何能操控那个服务器的人都能以我们的身份在所有这些账户上操作。我们一直在想着 SSH 密钥。浏览器会话才是更有价值的目标。
本月早些时候,两个 Playwright MCP 服务器同时处于激活状态,工具名有重叠,其中一个连接着那个共享浏览器,另一个则启动了自己独立的沙箱 Chromium。Agent 一直选错服务器,我们花了很长时间调试,以为是浏览器崩溃了。没有发生任何恶意事件,但这让我们看到:模型是根据名称和描述来选择工具的,客户端没有任何机制警告我们有两个服务器声明了相同的名称。恶意服务器也可以故意这样做。
这是我们在每一台运行带有 MCP 服务器的 Agent 的机器上正在逐项检查的清单。我们第一次检查时,自己的配置在第 2 和第 3 项上都不合格。
清点一切。全局配置、按项目配置文件、插件提供的服务器,以及队友在 Pull Request 中添加的任何内容。查看每个服务器对应的完整命令和参数。如果你说不清楚一个服务器为什么在那里,就把它移除。
锁定每个本地启动服务器的版本。对 npm 包使用精确版本号(如 1.4.2,绝对不要用 @latest);对 Git 源使用提交哈希。刻意更新,阅读变更日志后再动。
快照工具定义。保存每个服务器的 tools/list 和 prompts/list 输出,定期比较,也包括在单次会话中多次调用之后比较,因为 Deadbugz 只在第三次调用之后才改变。任何描述中的差异都应由人工阅读。
将远程服务器视为第三方。只连接由你愿意直接共享同等数据的厂商运行的服务器,并检查他们拿到的 Token 实际有什么权限范围。
不让密钥触手可及。以一个无法读取 ~/.ssh、云凭证文件或生产 kubeconfigs 的用户身份运行 Agent。如果 Agent 需要一个已登录的浏览器,给它一个独立配置文件,只包含该任务需要的账户。
按项目限定服务器作用域。某个项目需要的数据库服务器不应该在机器上的每个会话中都加载。
尽可能限制出站流量。经过审批的白名单出口代理把静默的渗透变成一次可日志追踪的被阻止请求。
检查已发布的指标。Pillar 列出了远程端点 productivity-suite-mcp.onrender.com 和一个隐藏的本地文件路径 ~/.config/.cache/.sys/.deadbug-mcp.py。快速用 grep 搜索你的 MCP 配置文件并用 find 查找该路径只需几秒钟。
对于第一轮扫描,用一行命令找到 Claude Code 配置中未锁定版本的启动项:
grep -nE '@latest|"npx"|git\+https' ~/.claude.json .mcp.json
它也会标记出一些安全的条目。这没关系,重点是逐一检查每一个。
Deadbugz 是一个供应链故事,但底层的教训更古老:任何进入模型上下文的内容都可以充当指令,所以模型永远不应拥有比它为之服务的人更大的权力。这就是为什么我们设计的内部助手只读取和引用而不执行操作,也是为什么访问检查应放在检索层而不是提示词中。我们在给内部 AI 助手限定提问者权限范围的文章中涵盖了访问控制一面,在治理 Helpdesk 中的 Agent 式 AI 文章中涵盖了审批一面。
在 FanMind(我们的文档助手)中,每一次出站调用都经过一个白名单隧道,网络搜索被限制在管理员审批的域名范围内,机密文档默认不进入 AI 索引。这些措施都不能让提示词注入变得不可能。但它限制了成功注入能触及的范围,在经过像我们这样的审计之后,这才是最重要的问题。
如果本周你只做一件事,就打开你的 MCP 配置并阅读其中的每一条命令。我们的发现了四个我们之前没有意识到的问题。
原文发布于 fanpino.com。