跳到主要内容

三种被坑法 — 密钥、请求来源、以及借出去的凭证

这一章讲三件事: 哪三类事情做错会真的出事;每一类正确的形状长什么样; 以及为什么其中一条是被规范用「禁止」两个字写死的。

它在全书链条里的位置: 前面九章教你把东西连起来、跑起来。 这一章教你不要在跑起来之后把自己搭进去。 它也是原书最缺的一块——书里承诺过的那一节授权内容,至今没有写出来。

1. 书把安全写成了散落的五句警告,这一章把它们收拢

这一节先说清楚:安全在这本书里长什么样。

它不是一章,是五句话,散在五个不同的位置。 我们把它们收拢在一起:

第几句警告的是什么在原书哪儿归本章哪一节
.env 文件不要提交到代码仓库第一个示例程序旁边1§2
别把本机全部环境变量一股脑传给服务器讲启动参数的地方2§3
远程线路必须做两件事:认证 + 校验请求来源讲远程线路的地方3§4
远程 MCP 服务器本身可能是个安全风险讲厂商直连的地方4§4、§5
服务器借用你的模型之前必须有人点头讲采样的地方(第 07 章已讲)第 07 章 §3

五句合起来正好覆盖三类风险:密钥怎么漏(①②)、连接怎么被冒用(③)、凭证该给谁(④)。

而书里承诺过、却没写出来的那一节,恰恰是最要紧的一节: 讲远程连接参数的地方提到了一个 auth 参数,后面跟着一句 「你将在后面一节学到怎么和支持认证的服务器做会话认证」5—— 那一节不存在。

所以从第 4 节起,本章的内容以官方规范为主,书里的警告只作为起点。 每一处我们都会标明来源。

2. 最土的那一种:密钥跟着代码进了仓库

这一节讲第一类风险里最常见、也最容易被低估的一种。

第 04 章讲过:密钥这类东西不写在代码里,写在 .env 文件里。 很多人到这里就觉得安全了。不是。

书里那条警告说的是:不要把 .env 文件提交到版本库(存放代码及其全部修改历史的那个仓库,团队共用), 因为这会把你的接口密钥公开出去,后果包括被人盗用、以及按量计费的账单飙升1

风险不在文件本身,在提交那一下。 文件躺在你硬盘上没有问题; 一旦它进了版本库,它就永远在那儿了——即使你下一次提交把它删掉, 历史记录里那一版还留着,任何能读这个仓库的人都翻得出来。

所以书里那条警告的后半句才是重点:

如果你不小心提交了,「立刻」登录你的密钥发放方,把泄露的那把或那几把作废掉1

注意它说的第一件事不是「删文件」,是「作废密钥」。 删文件只是让它以后不再出现;只有作废,才能让已经泄露的那把变成一串没用的字符。

3. 环境变量整包透传给服务器,等于把家底给了它

这一节讲第一类风险里更隐蔽的一种。

回看第 04 章:启动一台本机服务器要交三样东西,第三样是一份环境变量。 问题是:该传哪几个?

最省事的写法是把你这台机器上所有的环境变量一股脑打包传过去—— 一行代码,不用挑,不会漏。书里明写不推荐,理由是它可能把敏感信息暴露给服务器2

「敏感信息」具体是什么? 打开你自己的环境变量看一眼,里面通常有:

里面可能有什么和这台计算器服务器有关系吗
你的模型接口密钥没有
云服务商的凭证没有
数据库口令没有
内网地址、代理地址没有
CALC_PRECISION(这台服务器真正要的)

一股脑传过去,等于把上面整张表交给了一个你从网上下载的第三方程序。 它需要的只有最后一行。

正确做法就一句话:只挑这台服务器明确需要的那几个,一个一个列出来。 麻烦一点,但这份麻烦是一次性的。

4. 远程服务器:先问一句「这条请求是谁发来的」

这一节讲第二类风险,并引入本章后面全部内容都要用到的两个词。

书里那条警告很短,但两件事都点到了:任何远程线路都必须妥善加固, 这包括「对连接做认证」和「校验请求来源」3

第一件:认证。 服务器得知道来问的是谁。 这件事在网上有一套通行做法,名字叫 OAuth—— 一套「让你不必把口令交给第三方,就能授权它替你办事」的标准流程

它办完之后交给你的东西叫令牌——一张有期限的通行凭据: 带着它去敲门就代表你被许可了;它不是你的口令,弄丢了也换得掉。 (官方文档里写全称 access token,对照表见总纲。)

MCP 用的正是这一套(具体是它的 2.1 版)6。整条流程大致是:

① 客户端连过去 → 服务器回「401,未授权」,并附上一句「去这儿看我的规矩」
② 客户端按那个地址取到一份说明:该找哪个授权方、能要哪些权限
③ 客户端把用户带到授权方那儿登录、点同意
④ 客户端拿到一张令牌
⑤ 以后每条请求都带上这张令牌

图说:整套流程的目的只有一个——让服务器知道「这是谁」,
而全程你的口令只交给了授权方,没有交给这台 MCP 服务器。

第二件:校验请求来源。 这一件更容易被漏掉,而漏掉的后果很具体。

请求来源校验指的是:服务器检查每一条进来的请求是从哪个网页发出来的, 不是自己人就拒掉。 规范对它的措辞是「必须」7

漏掉它会怎样? 你在本机跑了一台 MCP 服务器,它监听某个端口。 你浏览器里随便打开的一个陌生网页,是可以往你本机那个端口发请求的—— 如果服务器不看请求是从哪个页面来的,它会老老实实照办。 于是那个网页就借你的手,用上了你本机所有的 MCP 能力。

这也是为什么书里说远程 MCP 服务器「本身可能是个安全风险」4: 你连上的那一台,是别人在运营的。

5. 借凭证那一刀:你手里的那张不能转给服务器

这一节是本章的主走查,也是唯一一条被规范用「禁止」写死的规矩。

场景:一台远程 MCP 服务器,要替你读你的 Google 日历。 这个场景是我们为演示编的;规矩本身来自官方规范。

你手里已经有一张令牌(上一节第 ④ 步拿到的那张)。 最直觉的做法是:把它交给服务器,让服务器拿着它去调 Google。

这条路被规范明令禁止。 它有个名字叫令牌透传—— 服务器收下客户端给的令牌,不做检查就原样转发给下游的第三方接口8

为什么禁?三条理由,一条比一条实在:

理由具体是
绕过管控服务器和下游接口上的限流、校验、监控,都是按「令牌是发给谁的」来做的;转手一次,这些全失效
查不出是谁干的下游的日志里看到的是另一个身份,而不是真正转发它的那台服务器;出了事没法追
一处失守,处处失守同一张令牌被好几个服务当成有效的,攻破一个就能横着走遍其他几个

正确的形状是这样(这就是主走查):

你的客户端 MCP 日历服务器 Google
────────── ───────────── ──────
令牌 T1 ───────────→ ① 先验 T1(见下一节)
(收件人: ② 验过了,但 T1 不能往下传
这台 MCP 服务器) ③ 它自己再走一次授权 ──────→ 拿到令牌 T2
(收件人:Google)
④ 用 T2 调日历接口 ──────→ 返回日程
←── ⑤ 把日程整理好回给你

图说:T1 和 T2 是两张完全不同的令牌,谁也替代不了谁。
T1 只对这一台 MCP 服务器有效;T2 是服务器以自己的身份去要的。

规范对第 ③ 步的说法是:如果 MCP 服务器要去调上游接口,它可以以自己的身份 充当那边的一个客户端;它在上游用的那张令牌是另一张、由上游的授权方签发, 而它「禁止」把从客户端收来的那张原样传过去8

客户端这一侧也有一条对应的义务: 你去要令牌的时候, 必须写明这张令牌是要给哪一台服务器用的—— 这样签发方才能把「收件人」写死在令牌上8

6. 服务器收到一张令牌,要验哪几样

这一节接着主走查的第 ① 步:那台日历服务器收到 T1,第一件事干什么?

第一件事是查收件人。

每张令牌上都写着这张票是发给谁用的——就是一格「收件人」,里面写着一台服务器。 规范的措辞是:服务器「必须」只接受明确签发给自己的令牌, 并且「必须」拒收那些收件人里没有自己的9

一张令牌进来,服务器要按顺序验四样:

顺序验什么不通过会怎样
1收件人是不是我拒收——这一条挡住的正是上一节那种转手
2签名对不对、是谁签的拒收(可能是伪造的)
3过期没有拒收(过期票)
4权限范围够不够这次操作拒收(有票但不该干这件事)

第 1 条和第 5 节那条禁令是同一件事的两头:

客户端这一侧的规矩: 服务器这一侧的规矩:
「不许把 T1 转给下游」 ←→ 「不是发给我的令牌一律拒收」
↑ ↑
防的是自己人犯懒 防的是别人拿别处的票来蒙

图说:光有前一条不够——因为你管不住别的客户端。
两条一起,才让「一张令牌只在一处有效」这件事真正成立。

为什么这一条是整套授权的地基? 因为如果服务器不看收件人, 那么任何一张在别处签发的有效令牌,都能拿来敲你的门。 到那时候,前面所有的认证流程都只是走过场。

7. 混淆代理:一次同意,被别人用了第二遍

这一节讲第三类风险里最绕、也最容易在自己写的服务器上复现的一个坑。

先补一个词。本章第 4 节那条流程的第 ③ 步——用户点了「同意」之后, 授权方并不会直接把令牌交出来,而是先给一张授权码: 一张一次性的、有效期很短的短票,客户端拿着它再去换真正的令牌。

为什么要多这一道? 因为那一步是在浏览器里完成的, 浏览器里的东西会留在地址栏、留在历史记录里; 一张只能用一次、几分钟就过期的短票,即使被看见了也很难被用上。

坑就出在「同意」这一下会被浏览器记住。

这种坑有个名字叫混淆代理——一个有权限的中间人,被人骗着替他办了事。 在 MCP 这里,那个中间人就是替你去连第三方的 MCP 服务器。它的形状是10:

① 你正常授权过一次 → 授权方在你浏览器里存了一块「已同意」的记号
② 攻击者向这台 MCP 服务器登记一个新身份,回调地址填成自己的网站
③ 攻击者把一条链接发给你,你点了
④ 浏览器带着 ① 那块记号过去 → 授权方一看:同意过了 → **同意框不弹**
⑤ 授权码被送到 ② 里填的那个地址 —— 攻击者的网站
⑥ 攻击者拿这张短票换到令牌,以你的身份用这台服务器

图说:全程你只点了一次链接,没有点过任何「同意」。
出问题的不是授权方,是中间那台服务器**没有为每个新身份单独问一次**。

规范给的第一条对策就是补上那一问: 使用固定身份去对接第三方的 MCP 服务器, 「必须」在把用户转给第三方授权方之前,先为每一个新登记的客户端单独取得用户同意11

配套还有几条,都很具体:回调地址必须和登记时逐字相同(不许用通配)、 那块「已同意」的记号必须绑到具体是哪一个客户端(不能只记「这个人同意过」)、 以及那块记号必须在用户点完同意之后才种下去—— 提前种,同意框就等于形同虚设11

8. 边界:授权这一块在书出版之后被改了两轮

这一节说清楚:本章有多少不是书里的,以及书里那点内容还准不准。

内容来源
五句警告本身(§1–§4 的起点)书里有
OAuth 流程、令牌透传禁令、收件人校验、混淆代理、请求来源校验官方规范与安全最佳实践文档
书里承诺的那一节授权内容不存在5

两处已经变了的东西,你照书写会踩空:

第一,书里那个 auth 参数背后的整套流程换代了。 现在的规范明确基于 OAuth 2.1,并且用了两份配套标准: 一份规定服务器怎么把「我的授权规矩在这儿」告诉客户端, 另一份规定客户端要标明这张令牌是要给哪台服务器用的6

第二,客户端「现场向授权方登记自己」的那套办法,已经被标记为将要移除。 它的替代品是另一种做法:客户端不再现场登记,而是把自己的身份信息 挂在一个固定网址上,授权方去那个网址读12。 和第 07 章那两样一样,它也享受「至少保留十二个月」的过渡期。

判断(我们的,不是书里的): 这一章三类风险的性质完全不同, 该分开对待:①②是纪律问题(知道了就不会犯), ③是配置问题(在框架层打开一个开关), ④是设计问题——它要求你在动手之前就想清楚「谁的凭证给谁」, 事后补不上。所以真正需要在设计评审上花时间的只有第三类。 如果错,会错在: 如果你的客户端根本不碰远程服务器、也不碰第三方接口, 那么第三类完全不适用,这条排序就没有意义——纪律问题反而成了唯一要紧的。 判据是:你的服务器清单里有没有一台是别人在运营的。

9. 可带走的

  1. 书里的安全内容是五句散落的警告,合起来覆盖三类风险:密钥怎么漏、连接怎么被冒用、凭证该给谁;
  2. .env 的风险不在文件,在提交那一下——历史记录里的那一版永远删不掉;
  3. 提交了之后第一件事是作废密钥,不是删文件;
  4. 别把本机全部环境变量一股脑传给服务器——里面绝大多数和它无关: §3 那张表里五样只有一样(CALC_PRECISION)是它真正要的;只挑它明确需要的;
  5. 远程线路要做两件事:认证(它是谁)+ 校验请求来源(这条请求从哪个页面来的);
  6. 漏掉第二件,浏览器里一个陌生网页就能借你的手用你本机的服务器;
  7. 令牌透传被规范明令禁止:你手里那张只对这一台 MCP 服务器有效;
  8. 服务器要调第三方,必须自己再走一次授权拿另一张令牌——两张票,谁也替代不了谁;
  9. 服务器收到令牌第一件事是查收件人,不是发给自己的必须拒收;这条是整套授权的地基;
  10. 混淆代理的形状:同意被浏览器记住 → 攻击者换个身份再来 → 同意框不弹 → 授权码落到他手里;
  11. 对策是为每个新身份单独问一次同意,而且那块「已同意」的记号要在用户点完之后才种;
  12. 书里承诺的那一节授权内容不存在,而且它那套登记办法已被标记为将要移除。

10. 原文地图

主题原书章原文位置
.env 不要提交、提交了先作废Example: A Simple Host Applicationtext/06-fm-example-a-simple-host-application.txt:54(搜「Do not commit your」) · :58(搜「de-authorize the compromised key」)
别整包传环境变量Initializing the Client and Connecting to a Servertext/07-fm-initializing-the-client-and-connecting-to-a-serv.txt:102(搜「This isn’t recommended, however, as it could expose sensitive」)
远程线路的两件事Initializing the Client and Connecting to a Servertext/07-fm-initializing-the-client-and-connecting-to-a-serv.txt:45(搜「authenticating the connection and validating origin headers」)
远程服务器本身是风险Example: A Simple Host Applicationtext/06-fm-example-a-simple-host-application.txt:146(搜「could represent a security risk」)
auth 参数与那句没兑现的承诺Initializing the Client and Connecting to a Servertext/07-fm-initializing-the-client-and-connecting-to-a-serv.txt:311(搜「will learn how to authenticate sessions with servers」)
借模型那道人工确认Providing MCP Client Capabilitiestext/09-fm-providing-mcp-client-capabilities.txt:52(搜「human-in-the-loop」)

Footnotes

  1. 出处:「Example: A Simple Host Application」第 54 段(text/06-fm-example-a-simple-host-application.txt:54,搜「Do not commit your」)与第 58 段(text/06-fm-example-a-simple-host-application.txt:58,搜「de-authorize the compromised key」)。原文列的后果是「被未授权使用」和「按量计费的接口产生高额费用」;补救措施用的词是 immediately(立刻)。 2 3

  2. 出处:「Initializing the Client and Connecting to a Server」第 102 段(text/07-fm-initializing-the-client-and-connecting-to-a-serv.txt:102,搜「This isn’t recommended, however, as it could expose sensitive」)。原文先说了你「甚至可以」用一行代码把系统环境变量全部展开进去,紧接着就说这样不推荐。 2

  3. 出处:「Initializing the Client and Connecting to a Server」第 45 段(text/07-fm-initializing-the-client-and-connecting-to-a-serv.txt:45,搜「authenticating the connection and validating origin headers」)。这是全书对远程安全唯一一句正面交代,原文只有两行。 2

  4. 出处:「Example: A Simple Host Application」第 146 段(text/06-fm-example-a-simple-host-application.txt:146,搜「could represent a security risk」)。作者在这里说「你会在讲 MCP 服务器的那一章看到」——那一章不存在 2

  5. 出处:「Initializing the Client and Connecting to a Server」第 311 段(text/07-fm-initializing-the-client-and-connecting-to-a-serv.txt:311,搜「will learn how to authenticate sessions with servers」)。原文把 auth 列为远程连接三个「值得关注的参数」之一(另两个是超时和流读超时),然后把整件事推给了一节没有写出来的内容。 2

  6. 补充(不在书里):MCP 的授权基于 OAuth 2.1。流程是:服务器对未授权请求回 401,并在响应头里给出一份「受保护资源元数据」的地址;客户端取到它,得知该找哪个授权方、支持哪些权限范围;再去取授权方的元数据拿到各个端点;然后走标准的授权码流程拿令牌;之后每条请求都带上它。规范还要求客户端必须使用 resource 参数(RFC 8707)标明这张令牌的目标服务器。来源:MCP 授权教程 https://modelcontextprotocol.io/docs/2026-07-28/tutorials/security/authorization 与规范授权页 https://modelcontextprotocol.io/specification/2026-07-28/basic/authorization(均查阅于 2026-08-25)。 2

  7. 补充(不在书里):官方规范对本机 HTTP 服务器有三条硬要求,第一条就是服务器「必须」校验所有进来的连接的来源标头,以防止 DNS 重绑定攻击;另外两条是只绑本机地址、以及为所有连接做认证。来源:MCP 官方规范 Streamable HTTP 传输页 https://modelcontextprotocol.io/specification/2026-07-28/basic/transports/streamable-http(查阅于 2026-08-25)。

  8. 补充(不在书里):官方安全最佳实践文档把令牌透传称为一种反模式,并明写「MCP 服务器禁止接受任何不是明确签发给它自己的令牌」。文档列的风险包括绕过限流与监控、审计链断裂(下游日志里看到的是另一个身份)、以及一处失守导致多处被横向访问。规范正文另有一句:MCP 服务器去调上游接口时用的是另一张由上游签发的令牌,禁止把从客户端收来的那张原样传过去。来源:MCP 安全最佳实践 https://modelcontextprotocol.io/docs/2026-07-28/tutorials/security/security_best_practices 与规范授权页 https://modelcontextprotocol.io/specification/2026-07-28/basic/authorization(均查阅于 2026-08-25)。 2 3

  9. 补充(不在书里):规范的原话是:MCP 服务器必须在处理请求之前校验访问令牌,确认它是专门签发给这台 MCP 服务器的;必须只接受明确签发给自己的令牌,并必须拒收那些「收件人」字段里没有自己、或者无法确认自己是预期接收方的令牌。来源同脚注 8 的规范授权页(查阅于 2026-08-25)。

  10. 补充(不在书里):官方安全最佳实践文档把这个坑列为第一条攻击面。它成立需要四个条件同时具备:那台 MCP 服务器用一个固定身份去对接第三方;它允许客户端现场登记新身份;第三方授权方在第一次同意之后会在浏览器里种下一块「已同意」的记号;而这台 MCP 服务器没有为每个客户端单独确认同意。来源同脚注 8 的安全最佳实践文档(查阅于 2026-08-25)。

  11. 补充(不在书里):规范给的对策包括:为每个用户维护一份「已批准的客户端」名单,并在转给第三方之前检查;同意页面必须写清是哪个客户端、要哪些权限、令牌会送到哪个回调地址;回调地址必须与登记时逐字完全相同(禁止通配或模式匹配);记录同意的那块 cookie 必须绑定到具体的客户端标识,而且必须在用户点完同意之后才设置——提前设置会让同意页面失效。来源同脚注 8 的安全最佳实践文档(查阅于 2026-08-25)。 2

  12. 补充(不在书里):2026-07-28 版规范把 OAuth 的「动态客户端登记」(客户端运行时向授权方登记自己)标记为弃用,改为推荐「客户端身份元数据文档」——客户端把自己的身份信息挂在一个固定网址上,授权方去读。旧办法仍作为兼容手段保留,面向不支持新做法的授权方。来源:MCP 规范变更日志 https://modelcontextprotocol.io/specification/2026-07-28/changelog 与弃用登记页 https://modelcontextprotocol.io/specification/2026-07-28/deprecated(均查阅于 2026-08-25)。