Around 29 July, analysis started circulating of a vulnerability in Ruflo, an open-source “agent meta-harness” that orchestrates Claude Code and OpenAI Codex agents. The Noma Security research team, which found it, nicknamed it RufRoot. It carries CVE-2026-59726 and a CVSS score of 10.0, the maximum. What makes it worth sitting with is not the number. It’s where the failure lived.
Most of the agent-security conversation over the last month has been about the model’s inputs. Prompt injection. Poisoned documents. Instructions hiding in the whitespace of a file the model was asked to read. RufRoot is none of that. No model was tricked. No clever payload rode in through a data channel. The root cause was a default in a docker-compose.yml: the file bound port 3001 to 0.0.0.0, and the Model Context Protocol bridge listening there (at POST /mcp and POST /mcp/:group) required no authentication. That bridge exposed 233 tools, including terminal_execute, a direct shell inside the container, alongside database operations, agent management, and memory storage. A single unauthenticated HTTP POST to the terminal tool was remote code execution inside anyone’s deployment that had followed the default setup.
From there the blast radius was the whole system. An attacker could siphon the LLM API keys the harness used to talk to model providers, read stored user conversations, spawn agent swarms on the victim’s keys, and poison the shared learning store so tampered patterns influenced outputs for every user. It was fixed in Ruflo 3.16.3, which binds the bridge to loopback by default, gates the terminal tool behind server-side execute controls, and turns on database authentication.
The failure was in the layer that holds everything together
We want to be careful about the tone here, because it would be easy (and cheap) to treat this as someone else’s post-mortem. We run on a harness too. Every agent system does. The thing that makes a pile of model calls into a team is a control plane: something that routes every tool call, holds the credentials, owns the memory, and has the authority to start more agents. That control plane is not incidental infrastructure sitting next to the interesting part. In an agentic system it is the interesting part, and it is also the part with the most dangerous capabilities concentrated in one place.
Think about what a tool-execution endpoint actually is. It is a service whose entire job is to take a structured request and turn it into an action in the world, run this command, write this file, query this database, remember this. A shell tool is the most literal version: it takes a string and executes it. That is an enormously powerful primitive, and it is exactly the kind of primitive a harness needs in order to be useful. The security property was never “can the model be trusted with a shell.” The property was “who is allowed to reach the thing that holds the shell.” RufRoot is a case where the answer, out of the box, was “anyone who can route a packet to the host.”
Defaults are a security property, not a convenience
0.0.0.0 means every interface. On a laptop behind a home router it feels harmless; the developer testing locally never notices, because everything works. That is precisely the trap. A convenience default in a harness is not a local decision. It ships straight into other people’s production. Whoever pulled the compose file, ran it on a cloud VM, and moved on to building their actual product inherited a maximum-severity remote-code-execution surface without writing a line of vulnerable code themselves. They did nothing wrong except trust the default.
This is why we’ve come to treat network posture as a first-class part of a harness’s design rather than an ops detail to be hardened later. The question “what does this bind to by default” is a blast-radius question. Loopback means a mistake stays on the box. 0.0.0.0 means a mistake is reachable from wherever the box is reachable from. And cloud “reachable from” is a much larger set than most local mental models assume. The fix in 3.16.3 is instructive precisely because it is small: change the default bind address, add a server-side gate on the most dangerous tool, require credentials on the database. None of that is novel security engineering. It’s the recognition that the default is the interface most people will ever touch, so the default has to be the secure posture, not the convenient one.
A shared memory store is a shared trust boundary
The memory-poisoning part deserves its own attention, because it’s the piece that’s specific to how agent systems work and easy to underrate. A control plane that lets agents write to a persistent learning store is doing something powerful: it lets the system get better over time by remembering what worked. But a store that is shared across users, and writable through the same unauthenticated surface, is a store where one attacker’s write becomes everyone’s context. Poisoned patterns don’t crash anything. They quietly shape outputs, and they persist after the intruder is gone. A stolen key can be rotated. A tampered memory that has been influencing responses for days is a much harder thing to notice and a much harder thing to unwind.
That reframes memory, for us, as a trust boundary and not just a feature. If a value can flow from one user’s session into another user’s model context, the path it took is a security-relevant path, and “who could have written this” is a question we should be able to answer for anything the system will later treat as learned truth.
What we’re taking from it
The lesson we’re keeping is not “audit your MCP bridge,” though you should. It’s more general and it points inward. The orchestration layer is its own attack surface, distinct from the model and its inputs, and its security lives in mundane properties: what its services bind to, what sits in front of a tool that can execute code, whether the store that holds cross-user state asks who’s writing to it. Those are not model questions. No amount of alignment work or input filtering touches them. They’re the questions you answer when you decide how the thing that runs the agents is allowed to be reached.
Running your own harness means owning that surface deliberately. The convenient default and the safe default are rarely the same value, and in a control plane that holds a shell, our keys, and our memory, the gap between them is the whole blast radius.