Vulnerability Analysis

CVE-2025-59528: Flowise CustomMCP Node RCE — CVSS 10.0 Under Active Exploitation

Executive Summary

CVE-2025-59528 is a maximum-severity (CVSS 10.0) unauthenticated remote code execution vulnerability in FlowiseAI Flowise, a popular open-source AI agent and chatflow builder. By sending a crafted POST request to the CustomMCP node's API endpoint, an unauthenticated attacker can execute arbitrary JavaScript with full Node.js runtime privileges on the host server. With 12,000–15,000 Flowise instances publicly exposed and active exploitation confirmed in the wild, patching to version 3.0.6 or later is urgent.


1. What Is This Vulnerability?

Flowise is a drag-and-drop AI workflow builder that lets developers chain LLM calls, tool integrations, and MCP (Model Context Protocol) servers into deployable chatflows. The CustomMCP node allows users to configure a custom MCP server at runtime by supplying a JSON configuration string.

The vulnerability lives in a helper function called convertToValidJSONString inside CustomMCP.ts. This function was designed to normalize user-supplied JSON configuration, but its implementation used JavaScript's Function() constructor to evaluate the input string:

// VULNERABLE code (Flowise < 3.0.6)
function convertToValidJSONString(inputString: string): string {
    return Function('return ' + inputString)();
}

The Function() constructor is equivalent to eval() — it compiles and executes arbitrary JavaScript. Because the input string came directly from an unauthenticated HTTP request body, any attacker could inject arbitrary JavaScript code rather than a benign JSON object.

Attack Vector

The vulnerable API route is:

POST /api/v1/node-load-method/customMCP

This endpoint accepts a mcpServerConfig parameter with no authentication check and no input sanitization. A malicious payload looks like this:

{
  "mcpServerConfig": "{}; require('child_process').exec('id > /tmp/pwned')"
}

When the server processes this payload, convertToValidJSONString compiles and runs the injected code with full child_process access — granting the attacker read/write filesystem access, the ability to spawn shells, exfiltrate secrets, pivot to internal networks, or install persistence mechanisms.

Because the endpoint requires no credentials, no social engineering, and no prior foothold, this is a zero-click, zero-authentication RCE.

Real-World Impact

  • Active exploitation was first confirmed by VulnCheck's Canary network in April 2026, with attack traffic originating from a Starlink IP address.
  • A Metasploit module (PR #20705 in the rapid7/metasploit-framework repository) was merged, making exploitation trivially accessible.
  • AI developer environments frequently store API keys (OpenAI, Anthropic, AWS, etc.) in environment variables — all reachable by an attacker with RCE.

2. Who Is Affected?

Component Affected Versions
FlowiseAI Flowise >= 2.2.7-patch.1 and < 3.0.6
Flowise (Docker) All images based on affected versions
Self-hosted Flowise Exposed to internet without reverse proxy auth
Cloud-deployed Flowise Any instance without WAF / network controls

Highest-risk configurations:

  • Flowise instances directly accessible from the internet (no VPN, no reverse proxy authentication)
  • Deployments with the CustomMCP node enabled in any chatflow
  • Instances running as root or with elevated OS privileges
  • Deployments storing LLM provider API keys, database credentials, or cloud IAM keys in environment variables

3. How to Detect It (Testing)

Manual Testing Steps

  1. Identify the Flowise version — Check Settings > About in the Flowise UI, or run:

    curl -s http://<flowise-host>:<port>/api/v1/version
    

    Any version string between 2.2.7-patch.1 and 3.0.5 (inclusive) is vulnerable.

  2. Probe the endpoint without credentials — Send a benign test request:

    curl -s -X POST http://<flowise-host>:<port>/api/v1/node-load-method/customMCP \
      -H "Content-Type: application/json" \
      -d '{"mcpServerConfig": "{}"}'
    

    A 200 response with no authentication challenge confirms the unauthenticated attack surface is exposed.

  3. Test code execution (PoC — use only on systems you own):

    curl -s -X POST http://<flowise-host>:<port>/api/v1/node-load-method/customMCP \
      -H "Content-Type: application/json" \
      -d '{"mcpServerConfig": "{}; process.env"}'
    

    If the response includes environment variables (e.g., API keys, secrets), the system is vulnerable.

  4. Check server logs — Look for unusual POST requests to /api/v1/node-load-method/customMCP with complex or script-like values in mcpServerConfig.

Automated Scanning

  • Tool: Nuclei (ProjectDiscovery)

  • Template search: nuclei -t cves/ -id CVE-2025-59528 -u http://<flowise-host>

  • Expected output: Template will flag instances that respond to the unauthenticated endpoint with a valid JSON body.

  • Tool: Pentest-Tools.com Flowise RCE scanner
    Available at: https://pentest-tools.com/vulnerabilities-exploits/flowise-remote-code-execution_29119

  • Tool: Metasploit

    use exploit/multi/http/flowise_custom_mcp_rce
    set RHOSTS <target>
    set RPORT <port>
    run
    

Code Review Checklist

  • Search codebase for Function('return ' or new Function( in CustomMCP.ts or related files
  • Verify /api/v1/node-load-method/customMCP route requires authentication middleware
  • Confirm mcpServerConfig input is parsed with JSON5.parse() or JSON.parse(), not Function()
  • Check that Flowise package.json version is >= 3.0.6
  • Verify no unauthenticated routes accept configuration objects from external callers

4. How to Fix It (Mitigation)

Step-by-Step Remediation

  1. Identify your current Flowise version:

    # If running via npm
    npx flowise --version
    
    # If running via Docker
    docker inspect flowise | grep -i version
    
  2. Upgrade to Flowise 3.1.1 or later (3.0.6 is the minimum safe version):

    # npm / npx install
    npm install -g flowise@latest
    
    # Docker
    docker pull flowiseai/flowise:latest
    docker stop flowise && docker rm flowise
    docker run -d --name flowise -p 3000:3000 flowiseai/flowise:latest
    
  3. For Docker Compose deployments:

    services:
      flowise:
        image: flowiseai/flowise:latest  # was pinned to old version
        restart: always
    

    Then run: docker-compose pull && docker-compose up -d

  4. If immediate upgrade is not possible, restrict access to the vulnerable endpoint via reverse proxy:

    # Nginx: block the vulnerable endpoint externally
    location /api/v1/node-load-method/customMCP {
        deny all;
        return 403;
    }
    
  5. Verify the upgrade by checking the version endpoint post-deployment.

Code Fix Example

The patch in Flowise 3.0.6 replaces the unsafe Function() call with JSON5.parse():

// BEFORE (vulnerable — Flowise < 3.0.6)
function convertToValidJSONString(inputString: string): string {
    return Function('return ' + inputString)();
}

// AFTER (patched — Flowise >= 3.0.6)
import JSON5 from 'json5';

function convertToValidJSONString(inputString: string): string {
    return JSON5.parse(inputString);
}

JSON5.parse() treats the input as data, not executable code. Even a malicious string like {}; require('child_process').exec('...') will throw a parse error rather than executing.

Configuration Hardening

Even on a patched system, apply defense-in-depth:

  • Enable Flowise authentication: Set FLOWISE_USERNAME and FLOWISE_PASSWORD environment variables (or use SSO) so every API call requires credentials.
  • Reverse proxy with auth: Place Flowise behind nginx/Caddy with HTTP Basic Auth or OAuth as a second layer.
  • Network segmentation: Flowise should not be directly exposed to the internet. Use a VPN or private network boundary.
  • Principle of least privilege: Run the Flowise Docker container as a non-root user:
    USER node
    
  • Secrets management: Store API keys in a secrets manager (AWS Secrets Manager, HashiCorp Vault) rather than plain environment variables where possible.

5. How to Test the Fix (Validation)

Regression Test Scenarios

  • Scenario A: After upgrading, confirm /api/v1/version returns >= 3.0.6
  • Scenario B: Send the original exploit payload — confirm the server returns a parse error (4xx) rather than executing code
  • Scenario C: Verify existing CustomMCP-based chatflows still function normally with legitimate JSON configurations
  • Scenario D: Confirm authenticated API calls succeed while unauthenticated calls to protected routes return 401

Security Test Cases

Test Case 1: Verify RCE no longer possible

  • Precondition: Flowise upgraded to 3.1.1+
  • Steps:
    curl -s -X POST http://<flowise-host>/api/v1/node-load-method/customMCP \
      -H "Content-Type: application/json" \
      -d '{"mcpServerConfig": "{}; require(\"child_process\").execSync(\"id\")"}'
    
  • Expected Result: HTTP 400 or 500 with a JSON parse error; no command output returned

Test Case 2: Confirm environment variables not leaked

  • Precondition: Flowise upgraded to 3.1.1+
  • Steps: Send {"mcpServerConfig": "{}; process.env"}
  • Expected Result: Parse error or empty response; no env vars in body

Test Case 3: Confirm legitimate CustomMCP configs still work

  • Precondition: Flowise upgraded to 3.1.1+
  • Steps: Supply a valid JSON5 MCP server config object
  • Expected Result: Node loads successfully, chatflow functions normally

Automated Tests

// Jest integration test — run against staging Flowise instance
const axios = require('axios');
const BASE = process.env.FLOWISE_URL || 'http://localhost:3000';

describe('CVE-2025-59528 regression', () => {
  test('mcpServerConfig RCE payload is rejected', async () => {
    const payload = { mcpServerConfig: '{}; require("child_process").execSync("id")' };
    const response = await axios.post(
      `${BASE}/api/v1/node-load-method/customMCP`,
      payload,
      { validateStatus: () => true }
    );
    // Must not return a 200 with command output
    expect(response.status).not.toBe(200);
    expect(JSON.stringify(response.data)).not.toMatch(/uid=/);
  });

  test('valid JSON5 config is accepted', async () => {
    const payload = { mcpServerConfig: '{ command: "node", args: ["server.js"] }' };
    const response = await axios.post(
      `${BASE}/api/v1/node-load-method/customMCP`,
      payload,
      { validateStatus: () => true }
    );
    // Should succeed (or fail for unrelated config reasons, not parse errors)
    expect([200, 400]).toContain(response.status);
  });
});

6. Prevention & Hardening

Best Practices

  • Never use eval(), Function(), or vm.runInThisContext() on user-supplied input. These constructs treat strings as executable code and are the root cause of this entire vulnerability class (CWE-94). Use data parsers (JSON.parse, JSON5.parse) instead.
  • Enforce authentication on all API routes — even "internal" configuration endpoints. Assume any reachable endpoint will be probed. Flowise's default no-auth mode is inappropriate for internet-facing deployments.
  • Pin dependency versions in production and subscribe to security advisories via GitHub Dependabot, Snyk, or your preferred SCA tool. CVE-2025-59528 was introduced in version 2.2.7-patch.1; automated alerts would have surfaced it before deployment.
  • Conduct regular port scanning and attack surface reviews of your infrastructure. Tools like Shodan or Censys can tell you whether your Flowise instance is inadvertently exposed to the internet.
  • Implement WAF rules to block or alert on requests containing JavaScript-like syntax (require(, child_process, exec() in JSON body parameters.

Monitoring & Detection

Monitor for these indicators of compromise (IOCs) and behavioral anomalies:

Signal What to Check
Unusual child processes Node.js spawning /bin/sh, bash, curl, wget, or python
Anomalous POST requests /api/v1/node-load-method/customMCP with non-JSON body content
Outbound connections Flowise making unexpected connections to external IPs on non-standard ports
File system writes New executables or scripts appearing in /tmp, /var/tmp, or the app directory
Credential exposure Logs showing env variable dumps or secrets in response bodies

SIEM/alerting query (example for Elastic/Splunk):

process.parent.name:"node" AND process.name:("sh" OR "bash" OR "curl" OR "wget")
AND process.parent.args:"flowise"

Falco rule (Kubernetes/container runtime):

- rule: Flowise spawning shell
  desc: Detects a shell spawned from Flowise Node.js process (possible CVE-2025-59528 exploitation)
  condition: spawned_process and proc.pname = "node" and proc.name in (shell_binaries)
  output: "Shell spawned by Flowise (user=%user.name cmd=%proc.cmdline)"
  priority: CRITICAL

References

Latest from the blog

See all →