A single unauthenticated HTTP POST to port 3001 gave full command execution inside the container. In the disclosing researchers’ words: “No token, no API key, no header check, no IP allowlist.”
CVE-2026-59726, codenamed RufRoot by Noma Labs, is a CVSS 10 in the Ruflo agent-orchestration platform. Its default deployment exposed the MCP bridge’s POST /mcp route, which accepted tool invocations and passed them straight to executeTool() with no authentication layer in front. Behind it sat 233 internal tools covering shell access, database operations, agent management, and memory storage.
The proof of concept is one curl:
curl -s -X POST https://<target>:3001/mcp \
-H "Content-Type: application/json" \
-d '{"jsonrpc":"2.0","id":1,"method":"tools/call",
"params":{"name":"ruflo__terminal_execute",
"arguments":{"command":"id && hostname"}}}'
The obvious write-up of this is “another agent platform shipped without authentication,” and six vendors have already published it. That version is true and not worth your time. There is a more uncomfortable one available, and it is uncomfortable for us.
The tool that matters is not the shell
Terminal execution is the headline and it is not the interesting part. Among those 233 tools was ruflo__agentdb_pattern-store — write access to the platform’s learning store. An attacker could inject malicious patterns into the learning pipeline, so that future conversations produced attacker-influenced output.
Sit with the shape of that. Shell access is loud, bounded, and ends when you evict the container. Memory poisoning is quiet, persists after the intrusion is remediated, and expresses itself later as the system behaving slightly wrong in the attacker’s favour. It does not look like a breach. It looks like the agent developing an opinion.
Everything I have argued this year says: hold state. Per-action checks cannot see a bad sequence of individually-fine steps, so accumulate risk across the run, keep it across sessions, evaluate the trajectory. RufRoot is the invoice for that position. A governance layer that accumulates state has, by construction, built a high-value target — and one whose corruption is harder to detect than the intrusion that caused it. Poison the store and you do not merely evade the evaluator. You conscript it.
The uncomfortable question, asked properly
If the argument for stateful governance is sound, the objection to it is equally sound and I would rather write it down than wait for someone else to: you are asking me to add a durable, high-privilege, security-relevant data store to my agent platform, on the strength of a threat model that says my agents cannot be trusted.
That objection is correct, and it does not have a clever answer. It has a boring one, which is that the state must be treated as a security boundary in its own right rather than as an implementation detail of the evaluator:
- Writes to the risk store are privileged operations, not a side effect of evaluation. If any tool in your registry can write to it, the registry is the risk store’s ACL, and 233 tools is the wrong number.
- The store needs its own audit trail, distinct from the one it feeds. A ledger that records everything except edits to itself is the classic gap.
- Poisoning should be detectable after the fact — meaning the accumulated state has to be reconstructible from an append-only record rather than trusted as a running total. If you cannot replay how a score got where it is, you cannot tell a compromised score from a correct one.
I am describing a standard, not a finished product. Our own composition module accumulates risk in a store and runs observe-only in production. It is subject to this critique, and stating the requirement is not the same as having met it.
What would and would not have helped
Being precise here matters more than being flattering, so plainly: nothing in decision governance would have stopped RufRoot, and nobody should claim otherwise.
This was an authentication failure at a network boundary. The bridge answered anyone who asked. There is no sequence to evaluate, no trajectory to accumulate, no composed risk to catch — the first request was already game over. This is the class where per-action controls are the entire answer, and where a composition layer is irrelevant. Any vendor telling you their governance product would have caught this is describing a control they do not have.
It is the same conclusion as the AISI incident, arriving from the opposite direction: per-action controls catch bad actions, stateful evaluation catches bad trajectories, and a serious deployment needs both. RufRoot is the purest available example of the first half.
The fix is the interesting part
Ruflo’s remediation (ADR-166 / PR #2521) is worth reading, because it did not merely add a token:
The MCP bridge now binds to loopback by default, and fails closed if you try to bind it publicly without setting MCP_AUTH_TOKEN. Bearer authentication middleware was added, terminal access was gated behind an environment flag, and containers were made read-only. Per the disclosure, a full fix was merged within a few hours.
The load-bearing choice is not the token — it is that an operator who misconfigures the exposure gets a refusal rather than an open port. The insecure state is now unreachable by accident. That is the same principle as our own first hard constraint, and I want to note it as convergence rather than vindication: two projects, no contact, same conclusion. It is a good sign for the norm and no evidence at all about either implementation.
What to check in your own stack this week
Three questions, in the order that will find something:
- Is your MCP bridge, tool registry, or agent control plane bound to a public interface? The default deployment is where this lived — not an exotic configuration.
- How many tools are reachable through it, and does any of them write to durable state the agent later reads? Those are not the same privilege and are frequently behind the same door.
- If someone had written to that store six weeks ago, how would you know today? If the answer is “we would notice the behaviour,” you would not.
Related reading
10 of 122: The Monitoring Was Not Built to Watch the Evaluation as It RanMCP Removed Protocol Sessions. State Is Your Job Now.
Our Security Scanner Passed an Agent That Leaked Root and an API Key
agentdb_pattern-store memory-poisoning path and the remediation details are taken from Noma Labs’ published advisory for CVE-2026-59726 (GHSA-c4hm-4h84-2cf3), cited inline and read directly rather than from a summary. No first-party incident data is presented, and no claim is made that any governance product would have prevented this vulnerability — the piece argues the opposite (HC-9). The critique of stateful risk stores applies to our own composition module, which is named in the text rather than exempted. Composition preprint: doi.org/10.5281/zenodo.21400261 · Enterprise Agent Architecture: doi.org/10.5281/zenodo.21105314.