2026-07-30 · view entry permalink →
CVE-2026-59726 (RufRoot) — Ruflo's MCP bridge took unauthenticated tool calls on all interfaces, and the memory it poisons is not cleaned up by the patch (CVSS 10.0)
Ruflo is an open-source AI agent orchestration platform that ships a chat interface, agent swarms, persistent memory and Model Context Protocol tool calling. Noma Labs describes the exposed surface as the platform's MCP bridge, the Express.js server handling every tool invocation, and its finding is blunt about the prerequisite: "a single unauthenticated HTTP POST request to port 3001 gave full command execution inside the container. No token, no API key, no header check, no IP allowlist. Nothing" (Noma Security, 2026-07-29). The flaw carries a CVSS base score of 10.0, and the maintainer's own advisory describes the same path: before 3.16.3 the default Docker Compose deployment exposed the bridge's tool-invocation endpoints without authentication, letting an unauthenticated network caller reach the terminal-execution tool, obtain a shell in the bridge container, read provider API keys, and write into the agent's learning-store patterns (Ruflo, 2026-07-01).
What turns this from a configuration mistake into a shipped default is the packaging. Noma states that "the docker-compose.yml binds port 3001 to 0.0.0.0 by default, exposing the bridge on all network interfaces" (Noma Security, 2026-07-29). An operator who followed the project's own quickstart and never made a deliberate decision to publish the service still had it listening — which is why the exposure question here is not "did we open a port" but "did we run the supplied compose file".
Three consequences follow from one request, and they escalate in how hard they are to reverse. Command execution inside the bridge container is the immediate one. Reading the provider API keys the platform holds is the second, and those credentials are valid outside the compromised host — they buy inference spend and, depending on the account, access to whatever else the key authorises. The third is the one that outlives the incident: instructions written into the agent's persistent memory continue steering the platform's behaviour for later users. The maintainer's advisory is explicit about the remediation consequence — "audit the AgentDB pattern store for injected agentdb_pattern-store entries; purge poisoned patterns. A patched redeploy alone does NOT undo poisoning" (Ruflo, 2026-07-01).
That last property is the reason this is worth handling out of cycle even though the fix has been available since early July and nothing is reported exploited. Ordinary patch hygiene assumes the update restores a known-good state. Here the authentication bypass closes and the memory contents persist, so an instance that was reachable during the exposure window comes back up patched and still carrying whatever was planted in it. The fix in 3.16.3 changes the defaults — an explicit authentication-token environment variable is now required before the bridge binds publicly, and the terminal-execution tool needs its own separate opt-in — but neither change inspects existing memory.
Noma Labs states it "disclosed responsibly, and within a few hours, Ruflo had a full fix merged" (Noma Security, 2026-07-29), with the maintainer's advisory published 2026-07-01 (Ruflo, 2026-07-01). Noma published both the single unauthenticated request that reaches code execution and the full eight-step impact chain it built on top of it (Noma Security, 2026-07-29) — nothing here requires reconstruction effort from a reader.
Detection is straightforward where the telemetry exists, because no legitimate anonymous caller should reach these endpoints. In application or reverse-proxy access logs, the signal is a POST to the bridge's tool-invocation paths carrying no authentication material, particularly one invoking the terminal-execution tool. On the host, the process-lineage anchor is a shell or interpreter spawned by the bridge's own Node.js process — expected for a platform whose purpose is running agent tools, which is precisely why the discriminator has to be the request that caused it rather than the spawn itself. Egress from the container to destinations that are not the configured AI providers is the third surface, since a stolen key is only useful once it is used somewhere.
Triage: distinguishing benign from malicious here turns on authentication and origin, not on what the agent does afterwards. A real user's tool call arrives through the platform's own UI with a session behind it; the anomaly is an unauthenticated POST straight to the bridge paths, from a source outside the host or its expected client range. For the memory-poisoning half there is no network artifact at all after the fact — the only check is content-level review of the pattern store for entries no operator or agent run accounts for.
A single unauthenticated HTTP POST request to port 3001 gave full command execution inside the container. No token, no API key, no header check, no IP allowlist. Nothing.
The docker-compose.yml binds port 3001 to 0.0.0.0 by default, exposing the bridge on all network interfaces.
Audit the AgentDB pattern store for injected agentdb_pattern-store entries; purge poisoned patterns. A patched redeploy alone does NOT undo poisoning.