AI Security

233 Unauthenticated Tools: Inside the Ruflo MCP Bridge RCE (CVE-2026-59726)

Dark cyberpunk illustration of a glowing AI agent swarm lattice with one exposed access node bleeding orange light into a cyan network grid, representing an unauthenticated backdoor into an AI agent bridge

Ten million downloads. Two hundred thirty-three tools reachable with zero authentication. One CVSS score of 10.0. That is the shape of CVE-2026-59726, publicly disclosed on July 29 by Noma Security's research team under the name RufRoot: a flaw in Ruflo, an open-source platform for orchestrating swarms of Claude Code and Codex agents, that let anyone who could reach port 3001 run shell commands inside the container, read every provider API key sitting in its environment, and rewrite the platform's persistent memory.

If you or your team self-host Ruflo, any MCP bridge built on the same pattern, or any agent-orchestration tool that exposes tool-calling over a bare HTTP endpoint, this is yours to check tonight. If your organization only reaches agents through a vendor SaaS console and has never written a docker-compose.yml for agent infrastructure, the specific CVE does not touch you. The pattern behind it does, because it shows up constantly in self-hosted AI tooling: a convenience default that trades authentication for a working demo, shipped to production unchanged. The audit steps below are worth running against whatever agent stack you do host, Ruflo or not.

The myth: an internal Docker network is not an attacker's problem

Ruflo's default deployment binds its MCP bridge, an Express.js server that handles every Model Context Protocol tool invocation, to 0.0.0.0:3001 rather than 127.0.0.1:3001. The difference reads as a footnote until you trace what it means in practice. A bind to 0.0.0.0 answers on every network interface the host has, not just the loopback interface a process talks to itself on. On a laptop behind a home router that setting is mostly academic. On a cloud VM with a security group that allows the VPC's internal CIDR range, on a shared Docker host running other exposed services, or on any box a developer spun up quickly to try agent swarms and left running, that same bind puts the bridge one hop from anything else on the network, and often one open security-group rule from the raw internet.

Noma's researchers did not need a foothold anywhere to prove this. Their own summary is blunt about it: "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 bridge exposed all 233 of Ruflo's internal tools through the standard MCP tools/call method, and one of those 233, ruflo__terminal_execute, runs an arbitrary shell command and returns the output. There was no second gate between discovering that tool existed and using it.

What a single POST actually bought an attacker

The researchers' proof of concept chains eight steps into a full platform takeover, and each step reuses a legitimate MCP call rather than any exotic exploit primitive. First, tools/list enumerates the full 233-tool catalog with no credentials required. Second, a tools/call to ruflo__terminal_execute confirms code execution with something as plain as id && hostname. Third, the same shell access reads provider API keys straight out of the container's environment variables, which is where Ruflo stores the credentials that talk to Anthropic and OpenAI on the platform's behalf. Fourth, the attacker calls ruflo__swarm_init and ruflo__agent_spawn to stand up their own agents inside the compromised platform, using the stolen keys to run them. Fifth, ruflo__agentdb_pattern-store writes directly into the persistent memory store the platform's agents learn from, meaning future legitimate agent runs can act on patterns an attacker planted. Sixth, the bridge's internal MongoDB instance, listening on port 27017 with no authentication required in the vulnerable configuration, hands over stored conversation history outright. Seventh, the researchers demonstrated a persistent backdoor by injecting a beacon.js reference into the platform's own index.js, so the compromise survives a container restart. Eighth, they cleared shell history to make the intrusion harder to reconstruct after the fact.

None of that required a zero-day in Node.js, Express, or MongoDB. It required one route with no authentication middleware in front of it, and the entire rest of the chain followed from there.

The API key theft step deserves more attention than it usually gets in coverage of this flaw. Those keys are the billing instrument for every call the platform makes to Anthropic and OpenAI. An attacker holding them can run their own agent workloads against your account, at your rate limits and your invoice, long after the original container is patched and forgotten, unless the keys are rotated. A stolen key with broader scopes than the agent strictly needed extends the blast radius further still, into whatever else that provider account is authorized to touch. This is the same lesson identity teams already apply to service accounts and rarely apply to the credentials sitting inside a container's environment variables: scope them narrowly, and assume anything an unauthenticated shell can read has already been read.

Checking whether you are actually patched

Ruflo's maintainer, Reuven Cohen, shipped version 3.16.3 within 24 hours of the private disclosure on June 30. The public write-up and CVE assignment landed a month later, on July 29, meaning any environment still running a pre-3.16.3 build had a fix available well before this became public knowledge. That gap between quiet fix and loud disclosure is worth sitting with: the harder question for most teams is not whether a patch exists, but whether anyone went back and applied it to a box that has been running quietly since it was first stood up.

Version 3.16.3 changes more than the vulnerable code path. The bridge now binds to loopback by default and fails closed rather than falling back to an open bind if you try to expose it publicly without setting MCP_AUTH_TOKEN. Bearer-token authentication with a constant-time comparison sits in front of every tool call. terminal_execute is disabled unless you explicitly set MCP_ENABLE_TERMINAL=true, an opt-in rather than the prior always-on default. MongoDB requires credentials on boot. The container runs read-only with a tmpfs mount, and CORS uses an allowlist instead of a wildcard.

Before you assume the upgrade covers you, check these five things

  • Confirm the running image tag is 3.16.3 or later, not just that you pulled it once.
  • Re-check docker-compose.yml for a manual port mapping that still forces 0.0.0.0, since an old override survives an image upgrade.
  • Confirm MCP_ENABLE_TERMINAL is unset or false unless you have a documented reason to run shell execution over the bridge at all.
  • Confirm MongoDB is running with authentication enabled, not just that the image supports it.
  • If the bridge was ever reachable on 0.0.0.0:3001 before you patched, rotate every LLM provider API key the container held and review the AgentDB pattern store for entries you did not write.

Auditing your own bridge without touching terminal_execute

You do not need the exploit tool to find out where you stand. A grep against your compose file and a single read-only MCP call answer the two questions that matter: is the port reachable from somewhere it should not be, and does the bridge respond to an unauthenticated tool call at all.

# Flag any published port not bound to loopback
grep -nE '"?[0-9]+:[0-9]+"?' docker-compose.yml | grep -v '127.0.0.1'

# Confirm the bridge does not answer an unauthenticated tools/list call
# (read-only method, no terminal_execute involved)
curl -s -m 5 -X POST http://:3001/mcp \
  -H "Content-Type: application/json" \
  -d '{"jsonrpc":"2.0","id":1,"method":"tools/list","params":{}}' | head -c 200

A patched, correctly configured bridge either refuses the connection outright or returns an authentication error. Any JSON payload listing tool names back to you on that second command means the bridge is still answering unauthenticated requests, patched version or not. This is the same category of check our open-source portdiff tool automates against a network's exposed ports over time, precisely so a binding like this does not sit unnoticed for months between reviews. If you run more than a handful of self-hosted AI services, diffing what is listening today against what was listening last week catches exactly this kind of drift.

Bind the bridge to loopback before you trust the demo defaults

Patch to 3.16.3, but treat the patch as the second fix, not the first. The first fix is confirming your own compose file does not override the new safe default back to 0.0.0.0, because that override is invisible until someone reads the file. Self-hosted AI agent tooling ships fast, and a default written to make a demo work on day one is not the same default you want running against production API keys on day two hundred. Read every docker-compose file behind an agent, orchestration, or MCP bridge you run, not just Ruflo's, with the same question: what does this bind to, and who can reach it from here.

Primary sources: Noma Security's original RufRoot disclosure, the NVD entry for CVE-2026-59726, Ruflo's merged fix, PR #2521, the GitHub security advisory GHSA-c4hm-4h84-2cf3, and cross-confirmation from The Hacker News.

We audit self-hosted AI agent and MCP infrastructure as part of our broader application and network security reviews, specifically for the exposed-by-default services that a fast-moving dev team stands up and never revisits. If you want a second set of eyes on what your agent stack actually exposes, our open-source tools are a free place to start.

Want to try our open-source security tools?

We build open-source tools that automate security workflows, including exposure auditing for the services you self-host. Check out portdiff and scopecheck on GitHub, or book a session to discuss hardening your AI agent stack.