check_resource_allowed treats /api/../admin as under /api

这个报错出现在 MCP Python SDK 的资源校验函数 check_resource_allowed 上:它只做字符串前缀比较,没有先归一化路径,导致 check_resource_allowed treats /api/../admin as under /api 。优先排查请求 URL 路

快速结论:这个报错出现在 MCP Python SDK 的资源校验函数 check_resource_allowed 上:它只做字符串前缀比较,没有先归一化路径,导致 check_resource_allowed treats /api/../admin as under /api。优先排查请求 URL 路径中是否包含 .、.. 或 %2e%2e 这类点段。

适用环境:Issue 中确认的环境为 MCP Python SDK,分支/提交 main @ 08a3bc8,涉及 mcp.shared.auth_utils 与 tests/shared/test_auth_utils.py。Issue 未提供操作系统、Python 版本、CUDA、显卡或其它依赖版本信息。

最快修复方案:暂无已合并的确认修复;该 Issue 被维护者以重复为由关闭,并指向 #3303 跟踪。评论中提到的可优先尝试方案是:在校验前对 requested_path 与 configured_path 做一次 urllib.parse.unquote() 百分号解码,再用 posixpath.normpath() 归一化(保证以 / 开头),然后重新应用尾斜杠归一化,最后保留现有 startswith 前缀规则。

注意事项:上述修复思路来自 Issue 评论中的本地实现与回归测试,并非 Issue 中已合并的官方修复。归一化必须同时作用于请求路径和配置路径,否则 /api123 对 /api 的路径边界防护可能被破坏。百分号解码只做一次,避免双重解码引入新的绕过面。是否采纳以 #3303 的最终处理为准。

问题场景

使用者调用 MCP Python SDK 的 check_resource_allowed,用配置的 https://example.com/api 校验请求资源 https://example.com/api/../admin 时,函数返回 True,即把已经走出 /api 前缀的路径当成仍在 /api 下。同类情况还包括请求 https://example.com/mcp/.. 对配置 https://example.com/mcp,以及百分号编码版本 https://example.com/api/%2e%2e/admin,均返回 True。该问题影响基于路径前缀的资源访问边界判断。

报错原文

check_resource_allowed treats /api/../admin as under /api

check_resource_allowed("https://example.com/api/../admin", "https://example.com/api")
# True today. I expected False.

requested `https://example.com/mcp/..` vs configured `https://example.com/mcp`
requested `https://example.com/api/%2e%2e/admin` vs configured `https://example.com/api`

原因分析

最可能的原因是 check_resource_allowed 在补齐尾斜杠后,直接使用 requested_path.startswith(configured_path) 做前缀比较,没有先折叠 . 和 ..。评论中的复现说明:尾斜杠补齐会把 /api/../admin 变成 /api/../admin/,该字符串仍以 /api/ 开头,因此前缀检查通过,但语义上 /api/../admin 实际解析为 /admin,已经越出 /api 前缀。百分号编码的 %2e%2e / %2E%2E 同样不会被前缀检查识别。

环境排查

  • 确认使用的 MCP Python SDK 是否处于 main @ 08a3bc8 或包含该行为的版本。
  • 确认问题入口为 mcp.shared.auth_utils.check_resource_allowed。
  • 检查调用方传入的资源 URL 路径是否可能包含 .、..、%2e、%2e%2e。
  • 运行 tests/shared/test_auth_utils.py,确认现有路径边界用例(如 /api123 对 /api)是否仍然通过。
  • Issue 未提供 Python、操作系统、依赖版本信息,无需按版本号排查。

解决步骤

  1. 在本地复现:调用 check_resource_allowed("https://example.com/api/../admin", "https://example.com/api"),确认当前返回 True。
  2. 确认跟踪状态:该 Issue 已被维护者标记为 #3303 的重复项并关闭,修复应跟随 #3303。
  3. 若需要自行修补,可优先尝试评论中给出的归一化方案:对 requested_path 和 configured_path 先执行一次 urllib.parse.unquote() 百分号解码,再执行 posixpath.normpath() 并保证结果以 / 开头。
  4. 重新应用尾斜杠归一化,保持 /foo 与 /foo/ 等价,确保 /api123 不会被 /api 前缀误匹配。
  5. 在归一化之后,再执行原有的 requested_path.startswith(configured_path) 判断。
  6. 补充回归用例,至少覆盖:越界点段(..、%2e%2e、../../admin、/mcp/..),以及归一化后仍合法停留在前缀内的路径(/api/v1/../v2、/api/./v1)。
  7. 运行 tests/shared/test_auth_utils.py,确认 16 个既有 auth 测试仍全部通过。

验证方法

修补后再次调用 check_resource_allowed("https://example.com/api/../admin", "https://example.com/api"),应返回 False;/mcp/.. 与 /api/%2e%2e/admin 对各自配置前缀也应返回 False。同时确认归一化后仍处于前缀内的 /api/foo/../v1 对 /api 仍返回 True,/api123 对 /api 仍被拒绝,且 tests/shared/test_auth_utils.py 全部通过。

参考来源

modelcontextprotocol/python-sdk #3464

modelcontextprotocol/python-sdk #3303

GamsGo AI

AI 工具推荐

想把多个 AI 模型放在一个入口?

GamsGo AI 集成 ChatGPT、DeepSeek、Gemini、Claude、Midjourney、Veo 等常用模型,适合写作、绘图、视频和日常 AI 工作流。

了解 GamsGo AI

推广链接:通过此链接购买,我可能获得佣金,不影响你的价格。

这个方案解决了吗?

celebrityanime
celebrityanime
文章: 28973

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注