Aug 25, 2026·10 min read·4 visits
Unauthenticated command injection vulnerability in Chainlit's `/mcp` endpoint allows remote attackers to execute arbitrary system commands via shell execution arguments inside the `stdio` transport parameters.
CVE-2026-45018 is a critical command injection vulnerability in Chainlit's Model Context Protocol (MCP) stdio transport backend. By submitting a crafted JSON payload containing dangerous argument options to an unauthenticated HTTP endpoint, a remote attacker can bypass executable validation rules and run arbitrary shell commands with the privileges of the active Python process.
Chainlit is an open-source Python framework designed to streamline the development of production-ready conversational artificial intelligence applications. It acts as an integration layer between downstream large language models (LLMs) and front-end user interfaces, providing developers with session management, data persistence, multi-modal file processing, and custom execution hooks. Because of its versatility, it is widely used in enterprise environments to deploy chatbots, copilots, and autonomous agent workflows. To expand its integrations with external systems, the Chainlit development team introduced support for the Model Context Protocol (MCP), an open standard designed to facilitate secure, bi-directional communication between AI applications and external data sources or tools.
The integration of the Model Context Protocol introduced a dedicated web endpoint, /mcp, within the FastAPI-based backend router. This endpoint was designed to handle client requests for spawning and connecting to MCP transport channels, which facilitate the message exchange. Among the supported transport channels, the standard input/output (stdio) transport allowed the application to execute local binaries and establish standard input (stdin) and standard output (stdout) channels for real-time IPC. When the MCP feature was enabled, the /mcp endpoint was exposed to the network and, by default, did not require any authentication token or active session check.
This architectural arrangement created a significant and highly exposed attack surface. Any remote client with network route accessibility to the Chainlit web service could interact with the /mcp endpoint and submit payload configurations containing execution parameters. Because the application was designed to dynamically instantiate command-line tools based on these client-supplied parameters, the input validation and validation architecture in this specific component was critical to maintaining the host's security boundaries. The total lack of validation on the arguments passed to these binaries directly led to the unauthenticated command injection vulnerability tracked as CVE-2026-45018.
The root cause of CVE-2026-45018 is a fundamental failure of input validation and sanitization prior to passing client-supplied arguments to an operating system execution sink. When a client initiated a connection via the stdio transport, the application requested a parameter named fullCommand. This string parameter was intended to represent the command-line instruction needed to launch the local tool acting as the MCP server. To prevent the arbitrary execution of unauthorized binaries, the application implemented a validation routine within the validate_mcp_command() function in the backend/chainlit/mcp.py module.
The validation routine used Python's native shlex.split() library to parse the user-controlled fullCommand string into distinct command-line tokens, separating the executable binary from its associated parameters. The logic then isolated the executable's base name by splitting the directory separators (slashes and backslashes) to check it against an allowlist. This allowlist was configured via the allowed_executables property under the features.mcp.stdio settings block. If the base name of the executable matched an entry in the list, the validation check succeeded, and the array of command-line tokens was returned for subprocess creation.
Two fatal vulnerabilities existed in this design. First, the validation routine only checked the primary token representing the executable's base name and performed zero validation, filtering, or sanitization on the subsequent tokens within the argument list. Second, if the allowed_executables configuration parameter was omitted by the administrator, the list defaulted to None. The application interpreted this None state as an implicit permit-all rule, allowing any binary present on the host filesystem to execute. This bypass mechanism allowed attackers to run arbitrary system utilities directly, bypassing the security boundaries.
Even in environments where a strict allowlist was explicitly configured with standard tools like the Node Package Executor (npx), the lack of argument validation remained fatal. npx is designed to download and execute Node.js packages on the fly and features advanced flags like -c or --call. These flags permit the user to pass an arbitrary shell script string to be executed within the host shell context. By capitalizing on these dual-use flags, a remote attacker could supply a validated binary name as the primary token while embedding an arbitrary command payload within the arguments, successfully routing past the executable check and executing system commands.
A thorough analysis of the codebase reveals the vulnerable input processing and the robust structural remediation implemented by the maintainers. In the vulnerable implementation, the API received a schema object defined as ConnectStdioMCPRequest which directly exposed the client-controlled command-line vector to the underlying operating system. This input was passed directly to the execution pipeline without parameter checking.
# Vulnerable Schema and Verification Logic
# File: backend/chainlit/types.py (Prior to fix)
class ConnectStdioMCPRequest(BaseModel):
sessionId: str
clientType: Literal["stdio"]
name: str
fullCommand: str # Threat vector: User-controlled command string
# File: backend/chainlit/mcp.py (Prior to fix)
def validate_mcp_command(full_command: str, allowed_executables: list):
parts = shlex.split(full_command)
if not parts:
raise ValueError("Empty command")
binary = parts[0]
# Extract only the base name of the executable
base_binary = binary.split("/")[-1].split(chr(92))[-1]
if allowed_executables is not None and base_binary not in allowed_executables:
raise ValueError(f"Executable {base_binary} is not allowed")
# Vulnerability: 'parts[1:]' containing arguments are returned without check
return partsThe fix, implemented in commit 0565fd0eccb915fce159929598b053ed79f6e0c9, completely removed the capability for clients to submit command-line instruction parameters. The entire fullCommand field was purged from the type schemas, and the corresponding parsing function was deleted. Instead, the server architecture was redesigned to enforce static server configuration files on the host filesystem. Clients can now only invoke a pre-configured server by reference using its unique identifier, ensuring that no client-controlled command strings can enter the execution context.
# Patched Implementation
# File: backend/chainlit/types.py (Version 2.12.0)
class ConnectMCPRequest(BaseModel):
sessionId: str
name: str # Client can only supply the configured server identifier
clientType: Optional[Literal["sse", "streamable-http"]] = None
url: Optional[str] = None
headers: Optional[Dict[str, str]] = NoneFurthermore, the patched version introduced a secondary security check during configuration load time. This verification mechanism uses the newly introduced helper function _find_leading_env_assignment to ensure that static command strings within the local configuration file do not contain malicious inline variable assignments or shell tricks. If a legacy, insecure config format is detected (such as the presence of old [features.mcp.stdio] properties), the server is designed to halt initialization immediately, alerting the administrator to update their configuration format.
Exploitation of CVE-2026-45018 is highly reliable and requires no authentication. The primary prerequisite is that the target Chainlit deployment has MCP support enabled in its configuration file. When this feature is active, the application exposes the unauthenticated /mcp route handler. Because session authorization checks were absent, any network-adjacent agent could deliver a crafted HTTP POST request containing a malicious payload to execute command pipelines.
To perform the attack, an exploiter selects an executable that is highly likely to exist on the host platform, such as the Node Package Executor (npx). The exploiter constructs an payload that specifies the clientType as stdio and passes a command string containing the target binary and shell redirection parameters inside the fullCommand field. The sequence of actions is mapped below:
Once the application server parses the payload and verifies the base name of the executable, it initiates a subprocess call using the full array of tokens. Because the execution of the command occurs during the initial startup phase of the transport layer, the payload is executed before the server attempts to negotiate the MCP protocol handshake. As a result, the command runs successfully even if the subprocess subsequently crashes or the TCP connection is terminated, making the exploit highly resilient against protocol-level errors.
The impact of successful exploitation of CVE-2026-45018 is unauthenticated remote code execution (RCE) on the host platform running the Chainlit application. Since the subprocess inherits the security context and system privileges of the active Python process, any command executed by the attacker runs with the exact same permissions. In environments where the server is poorly isolated, runs as a root user, or has access to local resources, this can lead to complete system compromise.
The CVSS v3.1 base score is assessed at 9.8, indicating the highest severity rating. In terms of confidentiality, an attacker can access the system's filesystem, including environment variables containing sensitive API keys for downstream LLMs (such as OpenAI, Anthropic, or custom database credentials). In terms of integrity, the attacker can overwrite application files, plant persistent backdoor scripts, or alter transactional databases. In terms of availability, the attacker can execute system shutdown procedures or consume excessive CPU and memory resources to trigger a denial of service.
In containerized and cloud-native environments, this compromise represents a potent pivot point. Attackers can leverage the service account tokens mounted inside the container filesystem to authenticate against internal Kubernetes cluster endpoints or cloud metadata services. This enables lateral movement within the enterprise network, potentially exposing adjacent microservices, internal code repositories, and confidential customer databases, demonstrating the severe systemic risk of this vulnerability.
The primary remediation path is to upgrade the Chainlit framework to version 2.12.0 or higher immediately. This version introduces the secure configuration architecture and completely eliminates the client-controlled command-line parameter interface. If upgrading is not immediately possible, administrators must disable the MCP feature entirely. This can be achieved by editing the .chainlit/config.toml file and setting the parameter value features.mcp.enabled = false, which unbinds the /mcp route handler and blocks exploitation.
For detection, security teams should implement logging filters to monitor incoming HTTP POST traffic to the /mcp endpoint. Payload inspections should look for JSON keys representing clientType: "stdio" paired with commands referencing common shell utilities or package execution tools like npx, python, bash, or sh. Host-based detection systems, such as endpoint detection and response (EDR) agents, should be configured to flag anomalous process lineage where the Chainlit Python process spawns interactive shell interpreters or unexpected network commands.
Network-level egress control represents an invaluable defense-in-depth mitigation. Since typical command injection payloads rely on establishing outbound TCP connections to pull external payloads or establish interactive reverse shells, blocking unauthorized outbound internet access from the application container will neutralize the majority of exploitation attempts. Restricting egress traffic to only approved third-party API endpoints, such as official LLM providers, significantly reduces the probability of successful compromise even if the system remains unpatched.
CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H| Product | Affected Versions | Fixed Version |
|---|---|---|
chainlit Chainlit | >= 2.4.0rc0, < 2.12.0 | 2.12.0 |
| Attribute | Detail |
|---|---|
| CWE ID | CWE-78 |
| Attack Vector | Network |
| CVSS Score | 9.8 (Critical) |
| Exploit Status | Proof-of-Concept |
| CISA KEV Status | Not Listed |
| EPSS Score | Not Available |
| Impact | Arbitrary Code Execution |
The software 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.
An arbitrary file read and write vulnerability exists in the Model Context Protocol (MCP) server endpoints of sublinear-time-solver and consciousness-explorer. By providing unvalidated file paths to the export_state, import_state, saveVectorToFile, and loadVectorFromFile tools, local attackers can read or overwrite sensitive host files.
An Authorization Bypass Through User-Controlled Key (CWE-639 / Insecure Direct Object Reference) vulnerability exists in @arikusi/deepseek-mcp-server starting in version 1.4.2 and fixed in 1.7.0. In Streamable HTTP transport mode, a process-global SessionStore singleton allows any remote client to retrieve or modify active conversation contexts belonging to other clients.
An unauthenticated server-side request forgery (SSRF) vulnerability exists in Chainlit versions >= 2.4.0rc0 and < 2.12.0 when the Model Context Protocol (MCP) features are enabled. This vulnerability allows remote, unauthenticated attackers to force the backend application server to initiate arbitrary HTTP/HTTPS connections to internal subnets, localhost endpoints, or cloud metadata infrastructure.
An algorithmic complexity denial of service vulnerability exists in the Python icalendar library's component equality evaluation. Due to recursive nested comparisons inside list membership operations, parsing and validating calendar components with deep nesting triggers exponential execution time, blocking application threads and consuming 100% of the available CPU core.
JupyterHub is vulnerable to an unauthenticated Denial of Service (DoS) vulnerability. Prior to version 5.5.0, form-based authenticators failed to restrict the size of the username input field on failed logins, allowing remote attackers to exhaust host storage and memory resources.
The self-hosted HTTP transport mode of @arikusi/deepseek-mcp-server (an MCP server for DeepSeek V4) exposes its JSON-RPC endpoint (POST /mcp) without authentication in versions 1.4.2 through 1.7.0. Unauthenticated clients can establish Model Context Protocol sessions and invoke tools, consuming the host's configured DeepSeek API key.