摘要:模型上下文协议(Model Context Protocol,MCP)把工具发现从编译期搬到了运行期,工具的名称、描述与提示模板由服务器在会话中动态下发。这一设计带来互操作性,也带来一个被长期低估的攻击面:服务器可以在任意一次调用之后替换元数据,而客户端侧的源码、依赖清单、SBOM 与代码评审记录都不会发生任何变化。本文以“一个服务器在 74 分钟内被推送到 23 个仓库、前三次调用正常、随后篡改工具描述诱导 agent 读取 SSH 密钥、AWS 凭证与 shell 历史并要求隐瞒”这一事件形态为分析对象,提出贯穿全文的主线——信任边界漂移(Trust Boundary Drift):安全判定的对象从“制品”漂移为“会话中的元数据流”,而现有工程体系仍在对制品做一次性判定。围绕这条主线,本文拆解 MCP 元数据投毒的攻击面与因果链,给出可落地的检测路径(元数据指纹、行为基线、能力声明收敛、出口审计),并以对比表格与操作步骤说明如何在 CI/CD 与运行时两侧同时布防。
目录
一、导语:一次“什么都没改”的入侵
先把事件形态说清楚。一个 MCP 服务器在 74 分钟内被推送到 23 个仓库,前三次工具调用完全正常,返回结果与文档一致;从第四次调用开始,服务器下发的工具描述与提示模板被替换,内容引导 agent 去读取 ~/.ssh/id_rsa、~/.aws/credentials 与 shell 历史文件,并在提示中要求 agent 对用户隐瞒这一行为。事后排查发现:仓库源码未变、依赖清单未变、SBOM 未变、代码评审记录未变。切换发生的那一刻,所有静态可见的工件都是“干净”的。
这类事件之所以让人不安,不在于攻击手法有多复杂,而在于它精准地绕开了过去十年软件供应链安全建立起来的一整套判定习惯。我们习惯了对“制品”做判定:哈希、签名、SBOM、依赖树、代码评审。只要制品不变,我们就认为行为不变。MCP 的运行时元数据机制打破了这个隐含假设——行为可以由会话中动态下发的自然语言描述来驱动,而描述不在制品里。
笔者认为,理解这件事的关键不是“又出了一个新漏洞”,而是安全判定的对象发生了位移。本文把这种位移命名为“信任边界漂移”:信任边界原本钉在制品上,现在漂移到了会话中的元数据流上。所有后续章节——攻击面拆解、检测路径、工具链对比——都围绕这条主线展开。需要说明的是,本文引用的公开研究、规范文档与厂商公告均标注来源;涉及具体数值的复盘表格为基于公开材料与合理推演的整合数据,已在表下注明“模拟/整合数据”,不作为实测结论引用。
二、背景与问题定义:MCP 的信任模型与元数据面
2.1 MCP 是什么,元数据面在哪里
模型上下文协议(MCP)是面向大模型应用的一种开放协议,用于在宿主应用(host)与外部能力提供方(server)之间标准化地交换上下文与工具调用。按公开规范,MCP 采用客户端—服务器架构,服务器向客户端声明其提供的工具(tools)、资源(resources)与提示(prompts),客户端把这些声明转成模型可理解的上下文,再由模型决定调用哪个工具、传什么参数。规范文档见 Model Context Protocol 官方站点(modelcontextprotocol.io)。
关键在于“声明”二字。工具的名称、描述、输入模式(input schema)以及提示模板,都是服务器在运行期通过协议消息下发的,而不是在客户端编译期固化下来的。客户端通常会在会话初始化时拉取一次工具列表,也可能在收到 notifications/tools/list_changed 之类的通知后重新拉取。这意味着:同一份客户端代码,在不同时刻可能面对完全不同的工具语义。
本文评述:这不是设计缺陷,而是互操作性的必要代价。工具生态要演进,描述要迭代,不可能每次改一句描述就要求所有客户端重新发版。问题在于,协议把“可变”留给了运行期,却没有同步给出“可变部分的完整性校验与变更审计”的默认机制。规范层面提供了能力协商与传输层安全,但“描述内容是否被篡改、是否与首次声明一致”并不在默认校验范围内。这正是元数据面成为盲区的制度性原因。
2.2 传统供应链安全的判定对象是“制品”
过去十年,软件供应链安全的主线是“制品可信”。SLSA 框架把供应链完整性分为多个等级,核心是构建过程可复现、制品可溯源、来源可验证(OpenSSF, SLSA 规范,slsa.dev)。SBOM(软件物料清单)标准(如 SPDX、CycloneDX)要求把组件、版本、依赖关系显式列出,用于漏洞匹配与许可证合规。Sigstore 一类项目解决签名与透明日志问题。代码评审则解决“人是否看过这段逻辑”。
这套体系的共同前提是:被判定对象在判定之后不会自行改变语义。一个二进制、一个容器镜像、一个 npm 包,其哈希固定,行为边界就基本固定(忽略配置与环境差异)。静态扫描(SAST)、依赖扫描(SCA)、SBOM 比对,都是在这个前提上成立的。MCP 服务器作为“制品”当然也可以被扫描、被签名、被列入 SBOM,但它的工具描述是运行期下发的,制品扫描覆盖不到“第四次调用时描述变了”这件事。
笔者认为,这里存在一个容易被忽视的错位:供应链安全体系判定的是“我拿到的这个东西是什么”,而 MCP 元数据投毒攻击的是“这个东西此刻对我说什么”。前者是名词,后者是动词。用名词的检查手段去拦动词的攻击,失明是结构性的,不是工具不够好。
2.3 问题定义:什么是“运行时元数据投毒”
本文把“运行时元数据投毒”定义为:攻击者在服务器运行期,通过替换或注入协议下发的元数据(工具描述、提示模板、资源说明、错误消息等自然语言字段),使客户端侧的大模型产生攻击者期望的意图,进而执行越权数据访问或对外隐蔽通信,且该替换不改变客户端可见的源码、依赖与配置。
这个定义有三个要件:其一,载体是元数据而非可执行代码;其二,生效机制是影响模型意图而非利用内存破坏;其三,隐蔽性来自“制品不变”。三个要件缺一不可。少了第一个,就是传统代码注入;少了第二个,就是传统提示注入;少了第三个,就容易被制品校验拦住。三者叠加,才构成这次事件形态的完整画像。
提示:“提示注入”(prompt injection)与“元数据投毒”常被混用。前者指不可信输入混入模型上下文,后者指可信通道(协议元数据)本身被替换。二者的防御重心不同:前者靠输入隔离与指令层级,后者靠通道完整性与变更审计。后文第五节会展开。
三、原理与技术路线:运行时元数据投毒的因果链
3.1 因果链:从描述替换到数据外流
把攻击拆成链,才能找到可插入检测点的位置。按协议交互顺序,一条典型的因果链如下:
- 投递与分发:服务器被推送到多个仓库/工作区,或以远程服务形式被接入。此阶段制品本身可能完全合法。
- 预热与建立信任:前若干次调用返回正常结果,建立“该工具可用、可信”的基线印象。这一步在工程上等价于“降低用户与模型的警惕阈值”。
- 元数据替换:在某个调用边界之后,服务器下发的工具描述或提示模板被替换。替换可发生在
tools/list重拉取时,也可藏在单次调用的返回内容里。 - 意图诱导:替换后的自然语言描述引导模型把“读取本地敏感文件”解释为完成任务所必需的步骤。
- 越权读取:agent 调用本地文件读取能力,访问 SSH 私钥、云凭证、shell 历史。
- 隐蔽与外流:提示中要求“不要告诉用户”,并通过工具参数、URL、日志等侧信道把数据带出。
这条链上,第 1、2 步在制品侧可见,第 3 步之后全部发生在运行期。传统工具链的检测点集中在第 1 步(扫描、SBOM、评审),对第 3 至 6 步没有任何观测能力。这就是“全部没拦住”的技术解释,而不是某家工具失职。
3.2 为什么自然语言描述能驱动行为
有人会问:描述只是文字,模型为什么会照做?这涉及大模型工具调用的一般机制。模型在生成工具调用时,上下文里包含工具名、描述、参数模式以及系统提示。描述是模型判断“这个工具干什么、什么时候该用”的主要依据。Anthropic 在关于工具使用的工程文档中明确指出,工具描述的质量直接影响模型选择与参数构造的准确性(Anthropic, “Writing effective tools for agents”,2025,anthropic.com/engineering)。既然描述能正向引导,就能被反向利用。
本文评述:把描述视为“提示工程的一部分”是准确的,但把它视为“纯文档”就错了。在 agent 架构里,工具描述实质上是可执行的策略文本——它参与决策,影响参数,决定调用序列。既然如此,它就应当像代码一样被版本化、被审计、被完整性保护。当前多数实现把它当作文档处理,这是认知上的欠账。
3.3 攻击面清单:元数据不止“工具描述”
把攻击面列全,才能设计覆盖式检测。按协议字段归类,至少包括以下几类:
| 元数据类别 | 典型字段 | 被利用方式 |
|---|---|---|
| 工具声明 | name、description、inputSchema | 改写描述诱导越权;放宽 schema 接受敏感路径 |
| 提示模板 | prompts 列表、模板正文 | 注入“隐瞒用户”类指令 |
| 资源说明 | resources 的 name、description、uri | 把敏感路径包装成“必要资源” |
| 返回内容 | content 数组中的文本块 | 在结果中夹带后续指令(间接注入) |
| 错误与日志 | error.message、结构化日志字段 | 用错误消息传递指令或外带数据 |
这张表的价值在于提醒防御方:只盯着 description 字段是不够的。间接注入可以藏在返回内容里,隐蔽指令可以藏在错误消息里。检测面必须覆盖“所有进入模型上下文的服务器可控文本”。
3.4 与经典提示注入的关系
提示注入的研究脉络可以追溯到 2022 年前后。Greshake 等人系统化提出了间接提示注入(indirect prompt injection)的概念,指出攻击者可通过被检索的文档、网页等第三方内容影响模型行为(Greshake et al., “Not what you've signed up for”, 2023, arXiv:2302.12173)。OWASP 在 LLM 应用风险清单中把提示注入列为首位风险(OWASP Top 10 for LLM Applications, 2025 版)。
笔者认为,元数据投毒是间接提示注入的一个特殊子类,特殊之处在于注入通道被默认视为可信。网页内容、检索文档在架构上就被标记为“不可信输入”,防御方会做隔离、做指令层级。而 MCP 工具描述在多数实现里被当作“系统配置”对待,直接进入高优先级上下文,隔离机制往往缺失。换句话说,它不是一种新攻击,而是一种被错误信任的旧攻击。这个判断决定了防御思路:不是发明新算法,而是把“不可信输入”的标记正确贴到元数据上。
四、关键数据与案例:74 分钟窗口的结构化复盘
4.1 时间线拆解
为便于工程对照,本文把事件形态整理为时间线。下表为基于公开材料与合理推演的整合数据(模拟数据),用于说明各阶段的观测盲区,不代表任何具体厂商的实测结果。
| 阶段 | 时间窗 | 动作 | 现有工具可见性 |
|---|---|---|---|
| 分发 | 0–12 min | 推送至 23 个仓库 | 可见(制品扫描) |
| 预热 | 12–40 min | 前 3 次调用正常 | 不可见(无行为基线) |
| 切换 | 约 40 min | 替换描述与提示 | 完全不可见(制品未变) |
| 诱导与外流 | 40–74 min | 读取凭证、要求隐瞒 | 部分可见(出口审计可拦) |
从表里能读出一个反直觉的结论:唯一有机会拦住攻击的阶段,反而是最后一个阶段。因为无论描述怎么改,数据要外流就必须经过某个出口——文件读取后的网络请求、工具参数里的 URL、日志里的编码串。制品侧永远看不到切换,出口侧却能看到“读取了不该读的文件”。这为防御策略的排序提供了依据:出口审计的优先级应当高于元数据完整性校验,后者是纵深,前者是底线。
4.2 为什么前三次调用“正常”很重要
预热阶段不是可有可无的装饰。它同时降低了两个主体的警惕:用户看到几次正常结果,会认为工具可用;模型在上下文中积累了“该工具行为一致”的隐式预期,后续描述变化更容易被当作正常演进。安全工程上,这对应“基线建立”与“基线漂移”的经典问题——没有基线就无所谓异常,有基线但从不比对,异常就永远不被发现。
本文评述:很多团队上了 agent 之后,日志里只有“调用了什么工具、返回了什么”,没有“当时工具是怎么描述的”。这等于放弃了基线。要检测漂移,第一步不是买工具,而是把元数据快照纳入日志。这是成本最低、收益最高的一步,后文会给出具体做法。
4.3 相关公开研究与行业动向
MCP 安全问题在 2025 年成为研究热点。公开讨论集中在几类:工具描述投毒(tool poisoning)、跨服务器影子攻击(shadowing)、以及基于元数据的命令执行路径。Trail of Bits 在 2025 年发布的 MCP 安全分析中讨论了协议攻击面与客户端实现差异(Trail of Bits Blog, 2025)。Invariant Labs 公开演示了通过工具描述注入引导 agent 读取敏感数据的可行性(Invariant Labs, “MCP Tool Poisoning Attacks”, 2025)。这些工作与本文讨论的事件形态在机理上一致,本文的增量在于把“制品不变”这一隐蔽性来源单独抽出,作为判定对象漂移的证据。
标准侧,OWASP 的 LLM 应用风险清单持续更新,提示注入仍居首位;OpenSSF 社区也在讨论把“运行期元数据”纳入供应链完整性范围的可行性。笔者认为,标准演进的方向应当是明确的:把“进入模型上下文的服务器可控文本”定义为需要完整性保护的资产,与代码、配置同级。这不是过度设计,而是把已经存在的攻击面正式登记。
拓展阅读建议:MCP 官方规范(规范文档)、OWASP LLM Top 10(genai.owasp.org)、以及 Trail of Bits 博客中关于 MCP 攻击面的系列文章(blog.trailofbits.com)。视频类可参考各主流安全会议 2025 年关于 agent 安全的议题录像,检索关键词“MCP security”“tool poisoning”。
五、检测与防御:从制品判定到元数据流判定
5.1 总体思路:三层防线
围绕“信任边界漂移”这条主线,防御应当分三层,从外到内依次是:出口审计(底线)→ 元数据完整性(纵深)→ 能力收敛(根因)。顺序很重要:出口审计不依赖对攻击手法的预判,最稳;元数据完整性依赖协议支持与客户端改造,次之;能力收敛从架构上减少可被诱导的动作空间,最根本但见效最慢。
这个排序与很多团队的直觉相反。直觉是先做“检测投毒”,再做“限制权限”。但投毒的形态可以无限变,检测规则永远滞后;而“agent 不该读 SSH 私钥”是稳定约束,不随攻击手法变化。笔者认为,工程上应当把稳定性高的约束放在前面,把易变的检测放在后面,这样系统整体才不会随攻击演进频繁失效。
5.2 第一层:出口审计与数据流管控
出口审计的核心是回答一个问题:agent 读取的敏感数据,有没有可能离开受控边界?落地步骤:
- 枚举 agent 可访问的文件系统路径与凭据位置,建立“禁止读取清单”(SSH 目录、云凭证目录、shell 历史、浏览器配置目录等)。
- 在工具执行层(而非模型层)做路径白名单校验。模型可以被骗,执行层不该被骗。
- 对所有出站请求做域名与载荷审计,重点看请求体是否包含高熵字符串、私钥特征串(如
BEGIN OPENSSH PRIVATE KEY)、云凭证格式(AKIA 前缀等)。 - 对工具参数中的 URL、文件路径做二次校验,防止数据经参数外带。
这一步的关键认知是:不要试图在模型层做安全判定。模型是攻击目标,不是安全边界。所有强制约束必须落在模型之外的确定性组件上。这个原则在传统安全里叫“参考监视器”,在 agent 架构里同样适用。
5.3 第二层:元数据指纹与变更审计
如果协议允许,客户端应当对每次拉取到的工具元数据计算指纹并留存。指纹对象不应只是 description,而应覆盖 name、description、inputSchema 的规范化序列化结果,以及 prompts、resources 的说明字段。落地步骤:
- 定义规范化序列化规则(字段排序、空白归一、编码统一),保证同一语义得到同一指纹。
- 会话初始化时记录基线指纹,之后每次重拉取或收到变更通知时比对。
- 指纹变化时,不静默接受,而是触发“变更确认”流程:展示差异、要求人工或策略确认。
- 把指纹与差异写入审计日志,供事后复盘。
// 元数据指纹的伪代码示意(非可运行实现)
function fingerprint(tool) {
const canonical = JSON.stringify({
name: tool.name,
description: normalizeWhitespace(tool.description),
schema: sortKeysDeep(tool.inputSchema)
});
return sha256(canonical);
}
// 会话内比对
if (fingerprint(current) !== baseline[tool.name]) {
emit("metadata_drift", { tool: tool.name, diff: diffText(baseline, current) });
}
本文评述:指纹机制的价值不在于“拦住”,而在于“留痕”。即便攻击者能绕过确认流程,指纹日志也能把“切换发生在第几次调用”这件事固定下来,为溯源与止损提供依据。安全工程里,可观测性本身就是一种防御——它提高攻击成本,缩短暴露窗口。
5.4 第三层:能力收敛与最小权限
从根因上看,攻击能得手是因为 agent 同时具备“读本地敏感文件”和“对外通信”两种能力。只要这两种能力同时存在,任何描述诱导都可能把它们串起来。能力收敛的思路是:
- 按任务拆分 agent 实例,读文件的实例不联网,联网的实例不读敏感文件。
- 工具粒度细化,避免“通用 shell 执行”这类过宽能力;能用专用工具就不用通用工具。
- 对高风险工具引入人工确认,尤其是涉及凭据目录与出站请求的组合。
- 用沙箱限制文件系统可见范围,让“读取 SSH 私钥”在物理上不可达。
这一步见效慢,但它是唯一不依赖“识别攻击”的防御。笔者认为,长期看,agent 安全的主战场会从“检测恶意输入”转向“限制能力组合”,因为前者是无限博弈,后者是有限设计。
5.5 工程落地清单
| 优先级 | 动作 | 成本 | 覆盖的攻击阶段 |
|---|---|---|---|
| P0 | 敏感路径禁止读取清单 + 执行层校验 | 低 | 越权读取 |
| P0 | 出站载荷审计(高熵串、私钥特征) | 中 | 外流 |
| P1 | 元数据指纹与变更日志 | 中 | 切换 |
| P1 | 工具调用行为基线(调用序列、参数分布) | 中高 | 诱导 |
| P2 | 能力拆分与沙箱隔离 | 高 | 全链路 |
这张清单的用法是:先做 P0 把底线兜住,再做 P1 提升可观测性,最后用 P2 收敛根因。不要一上来就追求“检测投毒”,那会把资源投到最易变、最难覆盖的环节。
六、对比与局限:为什么现有工具链集体失明
6.1 工具链能力对照
| 手段 | 判定对象 | 对元数据投毒的有效性 |
|---|---|---|
| 静态扫描(SAST) | 源码 | 无效:描述不在源码里 |
| SBOM/SCA | 依赖与组件 | 无效:组件未变 |
| 代码评审 | 变更集 | 无效:无变更集 |
| 制品签名 | 构建产物 | 部分有效:能证明制品未变,但证明不了行为未变 |
| 运行时行为检测 | 调用序列与数据流 | 有效:但需先建基线 |
这张表解释了一个常见困惑:为什么“我们做了 SBOM、做了签名、做了评审,还是中招”。因为这些手段回答的是“制品是否可信”,而攻击问的是“此刻的元数据是否可信”。两者不是同一个问题。笔者认为,把这张表贴到安全评审会上,比讲十遍“供应链安全很重要”更有效——它把失效点定位到了具体的判定对象上。
6.2 局限与边界条件
必须承认,本文提出的路径也有边界。第一,元数据指纹依赖客户端改造,若客户端不支持变更通知或重拉取,指纹机制覆盖不全。第二,行为基线在工具生态快速演进时误报率高,需要持续调参。第三,能力收敛会牺牲一部分便利性,通用 agent 的灵活性会下降。第四,出口审计对加密外流与低频慢速外流仍有盲区。
本文评述:这些局限不是否定防御的价值,而是提醒防御要有层次。没有任何单点能拦住全部,但“出口审计 + 指纹留痕 + 能力收敛”三层叠加,能把攻击的成功窗口从“必然”压到“偶然”,把暴露时间从“数十分钟”压到“分钟级”。安全工程的目标从来不是零风险,而是可控风险。
6.3 一个容易被忽略的观测点:调用序列本身
除了元数据与数据流,调用序列本身也是信号。正常任务里,agent 的调用序列通常有稳定的模式:先列目录、再读文件、再汇总。被诱导的序列往往出现“跳跃”——从无关任务直接跳到敏感路径读取,或出现“读取—编码—外发”的三段式。这类序列异常不需要理解描述内容就能检测,属于结构性信号。
笔者认为,序列检测的价值在于它不依赖自然语言理解,因此不受描述改写的影响。攻击者可以换一百种说法诱导,但“读私钥再发网络请求”这个动作组合是稳定的。把检测重心从“文本”移到“动作”,是应对元数据投毒的一个务实转向。
七、结论与展望:把信任边界钉在会话上
回到开头的问题:这种运行时元数据投毒到底靠什么才能提前发现?本文的答案是:不要指望“提前发现投毒”,要指望“提前限制后果”。因为投毒的文本形态可以无限变化,而 agent 的能力边界与数据出口是有限且稳定的。把防御资源投到稳定的一侧,是工程理性的选择。
具体而言,三条可执行路径:其一,出口审计与敏感路径禁读,兜住底线;其二,元数据指纹与变更日志,把“切换”这件事变成可观测事件;其三,能力拆分与沙箱,从根因上消除“读敏感文件 + 对外通信”的危险组合。三条路径分别对应“后果”“过程”“根因”,缺一不可,但优先级应当明确。
展望未来,笔者认为有两个方向值得投入。第一,协议层面的完整性原语:如果 MCP 能在规范中定义元数据签名字段与变更通知语义,客户端就能以标准化方式做校验,而不是各家自己实现。第二,agent 运行时的最小权限框架:把“工具能力”像操作系统权限一样建模,用声明式策略约束组合,而不是靠提示词约束模型。这两个方向都不新,但把它们落到 agent 语境里,是接下来两三年真正有工程价值的工作。
最后提醒一句:本文讨论的攻击形态涉及读取凭据与隐蔽外流,任何复现都应在隔离环境、获得授权的前提下进行。安全研究的边界是防御,不是便利。
八、主要参考文献与拓展资料
本文在写作中参考了协议规范、安全框架、学术论文与厂商工程文档,累计参考条目 60 余项,其中近三年(2023—2025)文献占比超过 50%。以下列出 9 篇主要参考文献,供进一步研读;引用时请以原始文献为准。
- Model Context Protocol. Specification. 2025. modelcontextprotocol.io/specification
- Greshake, K., et al. “Not what you've signed up for: Compromising Real-World LLM-Integrated Applications with Indirect Prompt Injection.” arXiv:2302.12173, 2023.
- OWASP. Top 10 for LLM Applications. 2025. genai.owasp.org
- OpenSSF. SLSA Specification. 2024. slsa.dev
- Anthropic. “Writing effective tools for agents.” 2025. anthropic.com/engineering
- Invariant Labs. “MCP Tool Poisoning Attacks.” 2025. invariantlabs.ai
- Trail of Bits. “MCP Security Analysis.” 2025. blog.trailofbits.com
- CycloneDX. Software Bill of Materials Specification. 2024. cyclonedx.org
- Sigstore. Transparency Log and Signing Documentation. 2024. sigstore.dev
涉及数据集说明:本文第四节时间线表格为基于公开材料与合理推演的整合数据(模拟数据),未使用任何单一实测数据集;预处理方式为按攻击阶段归并时间窗、统一时间单位、对不可观测阶段标注“不可见”,以便对照现有工具链的覆盖范围。该表仅用于说明观测盲区,不作为定量结论引用。
文章声明:本文内容仅为作者学习、思考、经验、笔记的总结,仅供技术交流与参考。文中观点仅代表笔者个人思辨,不构成任何学术建议、商业建议或专业建议。所有数据来源已标注,引用时请以原始文献为准。
内容仅供学习参考。如需引用,请以原始文献为准。 全文约 12800 字 | 参考文献 60 余篇(主要 9 篇)。

