deniz.in

Markets

Weather

Loading weather

· via dev.to (home feed)

Chainlit 2.12.0 patches unauthenticated RCE and SSRF in MCP endpoints

Chainlit 2.12.0 fixes CVE-2026-45018, an unauthenticated RCE via the MCP stdio transport, and CVE-2026-45019, an SSRF flaw; both require the non-default MCP flag.

Chainlit 2.12.0 patches unauthenticated RCE and SSRF in MCP endpoints

What happened

Chainlit shipped version 2.12.0 on 25 August 2026, closing two unauthenticated vulnerabilities in its Model Context Protocol (MCP) endpoints. According to a write-up on dev.to, the release fixes CVE-2026-45018, an unauthenticated remote code execution flaw in the MCP stdio transport with a vendor CVSS 3.1 score of 9.8, and CVE-2026-45019, a server-side request forgery issue in the SSE and streamable-http transports scored 7.2. Both sit behind the features.mcp.enabled flag, which has been off by default since Chainlit 2.7.0, so deployments that never switched MCP on are not in scope.

Researchers Vipin and Stephen at SPL Security reported the issues and demonstrated working proofs of concept against Chainlit 2.11.0, per the dev.to post. The affected range is 2.4.0rc0 through any version below 2.12.0 with MCP enabled. GitHub advisories GHSA-w3fx-mc44-mf6j and GHSA-hvfh-5mj3-5f3j track the two flaws.

How the command execution worked

With MCP switched on, a POST to /mcp accepted a fullCommand field from the client when the client type was stdio. The validation function in backend/chainlit/mcp.py split that string apart and compared only the executable's basename against an allowlist of launchers such as npx and uvx. Arguments were never inspected.

That gap mattered because common Node launchers can take a short flag that runs an arbitrary shell string. An attacker only needed an allowlisted binary name at the front of the command; everything after it was theirs to control. The command ran as the Chainlit user, and it executed before the MCP handshake failed.

The dev.to write-up adds a caution: the allowlist is not a safety net on its own. If allowed_executables is removed so its value becomes None, validation permits every executable. Shrinking the list does not fix the bug either. The actual fix in 2.12.0 is to stop accepting client-supplied commands altogether.

The SSRF sibling

CVE-2026-45019 lives in the same /mcp endpoint. For the sse and streamable-http client types, Chainlit took a raw URL plus optional headers from the caller with no scheme check, no host allowlist and no header denylist. The process then made outbound HTTP requests, including any auth headers the caller chose to send, toward internal hosts and link-local cloud metadata endpoints.

The dev.to post describes it as blind SSRF: response bodies stay inside the MCP client rather than returning directly to the attacker. Header forwarding on that path arrived in Chainlit 2.6.4, which widened what a caller could reach.

Who needs to act

Operators can check with pip show chainlit, or by printing chainlit.__version__ from the environment that actually serves the app. In .chainlit/config.toml, look for features.mcp.enabled. A version below 2.12.0 with that flag set is exposed. The post stresses restarting after upgrading so an old process is not still running the vulnerable code.

For teams that cannot upgrade immediately, the suggested mitigations are setting features.mcp.enabled to false, restricting egress from the host, and registering authentication so /mcp is not anonymous. Authentication alone does not remove command execution for a logged-in user on a vulnerable build; it only closes the anonymous path.

Breaking changes in 2.12.0

Upgrading requires configuration work. The old [features.mcp.sse], [features.mcp.stdio] and [features.mcp.streamable-http] sections, along with allowed_executables, now abort startup when MCP is on. Stdio servers must be declared under [[features.mcp.servers]] and clients connect to them by name. User-supplied SSE and HTTP servers need [features.mcp.user_servers] with enabled = true plus a non-empty allowed_urls list, and redirects are not followed, so the final HTTPS URL belongs in the config. After the change, anonymous clients can still launch developer-named stdio servers when auth is off, which is a pinned-command start rather than client-chosen code execution.

One discrepancy is worth noting: the dev.to post says the GitHub advisory pages still showed empty patched-version fields at research time, while the 2.12.0 release and Chainlit's in-tree MCP advisory name 2.12.0 as the fix.

Why it matters

Chainlit is a popular framework for building chat interfaces around AI applications, and MCP is increasingly how those apps reach external tools. An unauthenticated, 9.8-scored code execution hole on a path that AI deployments commonly expose is close to a worst-case scenario, tempered only by the default-off flag that limits the blast radius to operators who opted in. The bugs also illustrate a pattern likely to recur across the MCP ecosystem: allowlisting a launcher binary means little when the launcher itself executes arbitrary code from its arguments, and treating client-supplied URLs as fetch targets invites SSRF against internal networks and cloud metadata services. Anyone running Chainlit with MCP enabled below 2.12.0 should treat this as an immediate upgrade.

  • #chainlit
  • #mcp
  • #security
  • #vulnerability
  • #python

Related posts