Originally published at HOL
Chainlit 2.12.0 (2026-08-25) closes two bugs on /mcp
. CVE-2026-45018 is unauthenticated command execution via the MCP stdio transport. CVE-2026-45019 is unauthenticated SSRF via SSE and streamable-http. Both require features.mcp.enabled = true
. MCP is off by default since 2.7.0. If that flag is false, you can stop here.
features.mcp.enabled
is missing or false in .chainlit/config.toml
.This is not every MCP CVE in the ecosystem. It is not LiteLLM's authenticated MCP stdio test-endpoint issue.
With MCP on, POST /mcp
accepted a client-supplied fullCommand
when clientType
was stdio
. validate_mcp_command()
in backend/chainlit/mcp.py
split the string and checked only the executable basename against allowed_executables
(examples: npx
, uvx
). Arguments were not inspected. Common Node launchers can run an arbitrary shell string through a short flag, so an allowlisted basename plus client-controlled arguments still ran as the Chainlit user before the MCP handshake failed. SPL Security (Vipin and Stephen) reported working proofs against 2.11.0. Vendor severity for SPL-2026-001 / CVE-2026-45018 is CVSS 3.1 9.8. Affected range: >=2.4.0rc0, <2.12.0
with MCP enabled. GitHub advisory GHSA-w3fx-mc44-mf6j.
CVE-2026-45019 (SPL-2026-002, CVSS 3.1 7.2, GHSA-hvfh-5mj3-5f3j) is the sibling. For sse
and streamable-http
, the same endpoint took a raw url
and optional headers
with no scheme check, no host allowlist, and no header denylist. The process made outbound HTTP, including caller-controlled auth headers, toward internal hosts and link-local metadata endpoints. Blind SSRF: response bodies stay inside the MCP client. Header forwarding for that path arrived in 2.6.4.
If allowed_executables
is removed so the value is None
, validation allows every executable. The allowlist is not a safety net by itself.
Not default-on. Not fixed by shrinking allowed_executables
; 2.12.0 removes client-supplied commands. After upgrade, anonymous clients can still start developer-named stdio servers by name if auth is off. That is pinned-command start, not client-chosen RCE.
Run pip show chainlit
and print chainlit.__version__
from the same environment that serves the app. Open .chainlit/config.toml
and look for features.mcp.enabled
, allowed_executables
, user_servers
, and [[features.mcp.servers]]
. A Version below 2.12.0 with MCP enabled is exposed. Restart after upgrade so an old wheel is not still listening.
pip install --upgrade 'chainlit==2.12.0'
Then restart Chainlit. 2.12.0 breaks old MCP config: [features.mcp.sse]
, [features.mcp.stdio]
, [features.mcp.streamable-http]
, and allowed_executables
abort startup when MCP is on. Declare stdio servers under [[features.mcp.servers]]
and connect by name. User-provided SSE/HTTP needs [features.mcp.user_servers] enabled = true
and a non-empty allowed_urls
. Redirects are not followed; put the final HTTPS URL in config.
If you cannot upgrade today: set features.mcp.enabled = false
, restrict egress, and register auth so /mcp
is not anonymous. Auth alone does not remove command execution for an authenticated user on vulnerable builds; it only removes the anonymous path.
GitHub advisory pages still showed empty patched versions at research time; the 2.12.0 release and in-tree MCP advisory name 2.12.0 as the fix.
This is the operator write-up. The HOL Guard evidence page is the source record for CVE-2026-45018; sibling evidence for CVE-2026-45019 is at HOL Guard CVE-2026-45019.