Skip to content

CVE-2026-32871: Verified Repro With Script Download

CVE-2026-32871: FastMCP: path traversal to authenticated SSRF in OpenAPIProvider build url

CVE-2026-32871 is verified against fastmcp · pip. Affected versions: < 3.2.0. Fixed in 3.2.0. Vulnerability class: SSRF. This critical reproduction includes runnable sandbox proof, artifacts, and a plain-text agent view under REPRO-2026-00138.

REPRO-2026-00138 fastmcp · pip SSRF May 22, 2026 CVE entry ↗ .txt
Severity
CRITICAL
CVSS
10.0
Reproduced in
37m 15s
Tool calls
204
Spend
$2.20
01 · Overview

What Is CVE-2026-32871?

CVE-2026-32871 is a high-severity path-traversal-to-SSRF vulnerability in FastMCP's OpenAPIProvider, which lets a client redirect authenticated backend requests to arbitrary endpoints. Pruva reproduced it (reproduction REPRO-2026-00138).

02 · Severity & CVSS

CVE-2026-32871 Severity & CVSS Score

CVE-2026-32871 is rated critical severity, with a CVSS base score of 10.0 out of 10.

CRITICAL threat level
10.0 / 10 CVSS base
Weakness CWE-918 — Server-Side Request Forgery (SSRF)

Critical — the most severe class — typically remotely exploitable with severe impact. Treat as an emergency.

03 · Affected Versions

Affected fastmcp Versions

fastmcp · pip versions < 3.2.0 are affected.

How to Reproduce CVE-2026-32871

$ pruva-verify REPRO-2026-00138
or curl -O https://pruva.dev/api/v1/reproductions/REPRO-2026-00138/artifacts/reproduction_steps.sh && chmod +x reproduction_steps.sh && ./reproduction_steps.sh
Run in a VM or disposable container. This exploits a real vulnerability.
06 · Proof of Reproduction

Proof of Reproduction for CVE-2026-32871

Reproduced by Pruva's autonomous agents — 204 tool calls over 37 min. Full root-cause analysis and the complete transcript are below.

How the agent worked 722 events · 204 tool calls · 37 min
37 minDuration
204Tool calls
171Reasoning steps
722Events
1Dead-ends
Agent activity over 37 min
Support
22
Repro
332
Variant
363
0:0037:15

Root Cause and Exploit Chain for CVE-2026-32871

Summary

FastMCP's OpenAPIProvider exposes backend REST APIs as MCP tools. The RequestDirector._build_url() method substitutes client-supplied path parameters into the URL template by simple string replacement (str(param_value)) without URL-encoding. When a client passes traversal sequences such as ../../../admin, urllib.parse.urljoin resolves the resulting relative path against the base URL, causing the request to escape the intended API prefix and hit arbitrary endpoints on the same backend host — an authenticated SSRF because the provider's configured Authorization headers travel with the request.

Impact

  • Package: fastmcp (PyPI)
  • Component: fastmcp.utilities.openapi.director.RequestDirector._build_url()
  • Affected versions: < 3.2.0 (tested on 3.1.1 as the latest pre-fix release)
  • Fixed version: 3.2.0
  • Risk: High — any MCP client that can supply arguments to an OpenAPI-backed tool can redirect the outgoing HTTP request to internal endpoints, exfiltrating data or triggering unauthorized actions with the provider's own credentials.

Root Cause

In fastmcp < 3.2.0 the _build_url() implementation was:

url_path = path_template
for param_name, param_value in path_params.items():
    placeholder = f"{{{param_name}}}"
    if placeholder in url_path:
        url_path = url_path.replace(placeholder, str(param_value))
return urljoin(base_url.rstrip("/") + "/", url_path.lstrip("/"))

Because str(param_value) is inserted verbatim, a payload of ../../../admin in a template /users/{id}/profile yields a path string users/../../../admin/profile. urljoin then normalizes this relative to the base URL http://host/api/v1/ and resolves it to http://host/admin/profile, completely escaping the /api/v1/ prefix.

The fix in 3.2.0 replaces raw substitution with:

safe_value = quote(str(param_value), safe="").replace(".", "%2E")
url_path = url_path.replace(placeholder, safe_value)

quote encodes / to %2F and . to %2E, so traversal sequences become literal strings inside the path segment (e.g., %2E%2E%2F%2E%2E%2F%2E%2E%2Fadmin) and urljoin no longer interprets them as directory traversal.

Reproduction Steps

The reproduction script is repro/reproduction_steps.sh. It performs the following:

  1. Creates two isolated virtualenvs and installs fastmcp==3.1.1 (vulnerable) and fastmcp==3.2.0 (fixed).
  2. For each version, instantiates OpenAPIProvider with an OpenAPI spec that exposes only /users/{id}/profile.
  3. Retrieves the generated OpenAPITool named get_user_profile.
  4. Executes tool.run({'id': '../../../admin'}) against a custom httpx.AsyncClient using a recording transport so no real network is needed.
  5. Captures the final request URL and the HTTP path actually sent.
  6. Compares the vulnerable and fixed behavior and exits 0 when the vulnerability is confirmed.
Expected evidence
Build Request path sent by OpenAPITool
3.1.1 (vulnerable) /admin/profile — escaped the /api/v1/ prefix
3.2.0 (fixed) /api/v1/users/../../../admin/profile — traversal sequences are percent-encoded and remain inside the intended path

Evidence

Captured artifacts are stored under logs/:

  • logs/vuln_result.json — JSON record for the vulnerable run showing escaped_api_prefix: true and reached_unexposed_endpoint: true.
  • logs/fixed_result.json — JSON record for the fixed run showing escaped_api_prefix: false and reached_unexposed_endpoint: false.
  • logs/runtime_manifest.json — consolidated manifest with versions, URLs, and verdict flags.

Key excerpts from the vulnerable run (logs/vuln_result.json):

{
  "version": "3.1.1",
  "url": "http://127.0.0.1:9876/admin/profile",
  "path": "/admin/profile",
  "escaped_api_prefix": true,
  "reached_unexposed_endpoint": true
}

Key excerpts from the fixed run (logs/fixed_result.json):

{
  "version": "3.2.0",
  "url": "http://127.0.0.1:9876/api/v1/users/%2E%2E%2F%2E%2E%2F%2E%2E%2Fadmin/profile",
  "path": "/api/v1/users/../../../admin/profile",
  "escaped_api_prefix": false,
  "reached_unexposed_endpoint": false
}

Environment details:

  • Python 3.11
  • fastmcp==3.1.1 (vulnerable)
  • fastmcp==3.2.0 (fixed)
  • httpx (for the recording transport)

Recommendations / Next Steps

  1. Upgrade immediately to fastmcp >= 3.2.0.
  2. Validate input encoding — any code that builds URLs from user input should percent-encode path segments and encode . characters to prevent .. normalization.
  3. Regression tests — the existing TestPathTraversalPrevention test class in tests/utilities/openapi/test_director.py already covers this; ensure it remains part of the CI pipeline.
  4. Defense in depth — consider adding server-side URL path normalization checks or allow-listing in the reverse proxy / API gateway in front of the backend.

Additional Notes

  • Idempotency: repro/reproduction_steps.sh was executed twice consecutively and produced identical verdicts both times.
  • Edge cases tested: The same payload ../../../admin was used for both runs. The fixed version correctly encodes both slashes and dots, so double-encoded payloads (..%2F..%2Fadmin) are also handled safely because they are encoded a second time.
  • Limitations: The reproduction uses a synthetic httpx recording transport rather than a live MCP client/server handshake. This is sufficient because the bug lives entirely in the request-building layer (RequestDirector._build_url() → OpenAPITool.run()), which the script exercises end-to-end through the actual fastmcp code path.

CVE-2026-32871 Reproduction Transcript

The agent's step-by-step process — every tool call, every handoff, the moment the exploit fired.

Full session Replay every step — scrub the timeline or play it back.

Event 1/40
0:001:25
0:00
session startedaccounts/fireworks/models/kimi-k2p6 · cve-2026-32871 · cve-2026
0:09
0:10
0:11
web search
0:17
0:17
0:18
web search
0:23
0:24
1:08
1:08
extract_facts
no facts extracted
1:09
1:09
1:09
supportrepro
1:11
1:11
1:13
1:13
1:13
1:21
1:21
1:21
1:25

Artifacts and Evidence for CVE-2026-32871

Scripts, logs, diffs, and output captured during the reproduction.

No artifacts available

08 · How to Fix

How to Fix CVE-2026-32871

Upgrade fastmcp · pip to 3.2.0 or later.

Coming soon

Step-by-step mitigation and hardening guidance for CVE-2026-32871 — configuration checks, workarounds where no patch exists, and how to verify you're protected — is on the way.

10 · FAQ

FAQ: CVE-2026-32871

How does the FastMCP path-traversal SSRF attack work?

An MCP client calls an OpenAPI-backed tool and supplies a path parameter containing ../ segments (for example against a /users/{id}/profile template). RequestDirector._build_url() inserts the value unescaped and urljoin resolves the resulting relative path outside the intended API prefix, so the request is sent to an arbitrary endpoint on the same backend host — carrying the provider's own configured Authorization headers, making it an authenticated SSRF that can reach internal or undescribed backend APIs.

Which FastMCP versions are affected by CVE-2026-32871, and where is it fixed?

fastmcp < 3.2.0 is affected (tested on 3.1.1); it is fixed in 3.2.0.

How severe is CVE-2026-32871?

High — any MCP client that can supply arguments to an OpenAPI-backed tool can redirect the outgoing HTTP request to internal endpoints, exfiltrating data or triggering unauthorized actions using the provider's own credentials.

How can I reproduce CVE-2026-32871?

Download the verified script from this page and run it in an isolated environment against fastmcp < 3.2.0 (e.g. 3.1.1) configured with an OpenAPIProvider. It calls a tool with a path parameter containing ../ traversal sequences and shows the outbound request landing on an unintended backend endpoint with the provider's Authorization header attached.
11 · References

References for CVE-2026-32871

Authoritative sources for CVE-2026-32871 — official vulnerability databases and the upstream advisory. Pruva's reproduction verifies the issue firsthand; these are the primary records to corroborate it.