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.
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).
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 — the most severe class — typically remotely exploitable with severe impact. Treat as an emergency.
Affected fastmcp Versions
fastmcp · pip versions < 3.2.0 are affected.
How to Reproduce CVE-2026-32871
pruva-verify REPRO-2026-00138 curl -O https://pruva.dev/api/v1/reproductions/REPRO-2026-00138/artifacts/reproduction_steps.sh && chmod +x reproduction_steps.sh && ./reproduction_steps.sh 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
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 on3.1.1as 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:
- Creates two isolated virtualenvs and installs
fastmcp==3.1.1(vulnerable) andfastmcp==3.2.0(fixed). - For each version, instantiates
OpenAPIProviderwith an OpenAPI spec that exposes only/users/{id}/profile. - Retrieves the generated
OpenAPIToolnamedget_user_profile. - Executes
tool.run({'id': '../../../admin'})against a customhttpx.AsyncClientusing a recording transport so no real network is needed. - Captures the final request URL and the HTTP path actually sent.
- Compares the vulnerable and fixed behavior and exits
0when 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 showingescaped_api_prefix: trueandreached_unexposed_endpoint: true.logs/fixed_result.json— JSON record for the fixed run showingescaped_api_prefix: falseandreached_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
- Upgrade immediately to
fastmcp >= 3.2.0. - Validate input encoding — any code that builds URLs from user input should percent-encode path segments and encode
.characters to prevent..normalization. - Regression tests — the existing
TestPathTraversalPreventiontest class intests/utilities/openapi/test_director.pyalready covers this; ensure it remains part of the CI pipeline. - 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.shwas executed twice consecutively and produced identical verdicts both times. - Edge cases tested: The same payload
../../../adminwas 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
httpxrecording 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.
Artifacts and Evidence for CVE-2026-32871
Scripts, logs, diffs, and output captured during the reproduction.
No artifacts available
How to Fix CVE-2026-32871
Upgrade fastmcp · pip to 3.2.0 or later.
FAQ: CVE-2026-32871
How does the FastMCP path-traversal SSRF attack work?
Which FastMCP versions are affected by CVE-2026-32871, and where is it fixed?
How severe is CVE-2026-32871?
How can I reproduce CVE-2026-32871?
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.