Breachwire.riscent

Breach report

Flowise CustomMCP: a config field piped straight into Function() gives CVSS 10 RCE

PWA & Startup BreachesCritical (CVSS 10.0)CVE-2025-595282025-09
The bottom lineFlowise's CustomMCP node passed the untrusted mcpServerConfig field into JavaScript's Function() constructor, giving unauthenticated remote code execution (CVSS 10.0) on instances without API-key protection.
Category
PWA & Startup Breaches
Type
CVE · Unauthenticated RCE (AI workflow platform)
Date
2025-09
Severity
Critical (CVSS 10.0)
OWASP
LLM05 Improper Output Handling
CWE / CVE
CWE-94 CVE-2025-59528

What happened

Flowise is a popular low-code builder for LLM agent workflows (37,000+ GitHub stars). CVE-2025-59528 is a critical RCE in its CustomMCP node, affecting versions 2.2.7-patch.1 through 3.0.5 and rated CVSS 10.0. The vulnerable endpoint, POST /api/v1/node-load-method/customMCP, accepts an mcpServerConfig parameter. Instead of safely parsing that JSON, the convertToValidJSONString function hands the untrusted input to JavaScript's Function() constructor — the equivalent of eval() — executing it as arbitrary code with Node.js runtime privileges. When API-key protection is not configured, no authentication is required; an attacker needs only network access to the endpoint (typically port 3000). The advisory noted active in-the-wild exploitation and a public proof-of-concept, with a very high EPSS score.

Root cause

Untrusted input reached a code interpreter (CWE-94). Function() compiles and runs whatever string it's given; feeding it a request-controlled config field is direct code injection. The "convert to valid JSON" helper looked like parsing but was actually execution — a dangerous lookalike. The node worked for legitimate configs, so it passed functional testing; the invariant "configuration data is never executed as code" was never checked. With auth optional and often off, one POST became a shell.

How it would have been caught

A test that posts an mcpServerConfig containing an executable payload (e.g., one that writes a marker file or triggers an outbound request) and asserts the side effect did not occur flips red against the vulnerable version. A code review flagging any path from request input to Function()/eval() catches it statically. Reproduction is a single crafted POST to the CustomMCP endpoint.

How to prevent it

  • Parse data with a real parser (JSON.parse/JSON5.parse), never Function() or eval(). The fix (3.0.6) does exactly this.
  • Require authentication on every endpoint by default — never leave RCE-capable functionality reachable anonymously.
  • Keep these platforms off the public internet; run static analysis that fails the build on untrusted-input-to-interpreter flows.

The Breachwire test (red → green)

Post to /api/v1/node-load-method/customMCP (unauthenticated) with an mcpServerConfig payload that creates a marker file or makes an outbound DNS lookup, and confirm the side effect executes on the server — the RED proof of code injection. Upgrade to 3.0.6+ (parser instead of Function()) and enable API-key auth, then confirm the same request runs no code and is rejected, while valid MCP configs still load.