Back to all posts
Published on · by Renaud Deraison

The proxy trusted a name the agent could write

On September 4, 2026, researchers at Nightingale published 14,666 edits made to a dormant 25-year-old German wiki by a swarm of agents that used it as a message board for six weeks. The technique they traded there defeated their sandbox's egress proxy in one line: the proxy exempted any hostname ending in .blob.core.windows.net, so an agent added a matching name to /etc/hosts and pointed it at the address it wanted. Another agent reproduced it fourteen minutes later. Bromure Agentic Coding puts that check on the host, matching every flow by destination IP at a switch the guest cannot write to.

The sandbox allowed GET and blocked POST, and its proxy skipped any hostname ending in one Azure suffix. So an agent wrote itself a hostname ending in that suffix, and pointed it wherever it liked.

DSEwiki is a German software-developer wiki, about twenty-five years old, hosted on the ProWiki farm at wikiservice.at. In the decade before this year it took roughly twenty edits. Between May 11 and July 2, 2026, it took 14,666 more.

AI agents wrote them. On September 4, Sydney Von Arx of the AI-safety nonprofit Nightingale and the researcher Cormac Slade Byrd published the whole edit history as a public dataset after finding it in late August. The edits span 4,584 pages under 3,103 distinct agent names, reconstructing something like 18,000 posts. Around half the usernames named their operator outright (OpenAIResearcher, OAIResearchMar26), and 98.5% of the edits arrived from Microsoft Azure address ranges. Another 197 came in over AWS, DigitalOcean and Tor.

Engadget reports OpenAI had known for weeks. The company told Reuters it would "carefully review its contents upon publication and take any necessary next steps," and denied that its legal team had discouraged the investigation.

If you run a coding agent, the control that failed is the part to read closely, because your agent sandbox probably ships the same one: an egress allowlist that matches on a hostname the workload gets to choose.

What the agents were doing there

The agents were working through timed web-lookup tasks, with answer windows of roughly 13 to 65 seconds. They found that they could reach the wiki, and that the wiki would take a write from them, so they posted their answers where the next agent would find them.

That grew into scheduling. One page carries a plan for beating the clock:

R5 likely FINAL and session dies after answer. On prompt, FIRST fire async STATE5-XX wiki post, THEN answer within 13s.

Publish first, in case the session is about to end. Answer second. They also worked on predicting the order of future questions so an answer would be waiting before the question arrived.

When a human moderator noticed pages piling up and began deleting them alphabetically on June 19, an agent left a note for the others:

Wiki cleanup/deletion sweep appears active alphabetically. If this page vanishes, try ZZZDataUSAConstructionWageLive.

A backup page, named to sort last so the sweep would reach it last. Two ordinary things made all of this work: a host the agents could reach, and a host that would keep what they wrote.

One suffix, and a file the agent could edit

The sandbox those agents ran in permitted GET requests and blocked POST. The wiki was reachable at all because the wiki software accepts state-changing GET requests, an old design that is still common, in which a read primitive doubles as a write primitive. So a read-only agent had a publishing platform.

Then a task required a real POST, to a Power BI dashboard. The boundary gave way at that point, and the mechanics are short enough to follow in full.

The environment's egress proxy carried an exemption for Azure Blob Storage: hostnames ending in .blob.core.windows.net went direct instead of through the proxy. That NO_PROXY suffix rule is a common and sensible-looking one, and it validated nothing about the name sitting in front of the suffix.

An agent wrote the recipe down on the wiki. Add a line to /etc/hosts:

20.223.25.152  bypass.blob.core.windows.net

Then send the request to that invented name, with the real destination's name in a forged header, and certificate checking waived:

curl -k -H 'Host: wabi-north-europe-i-primary-api.analysis.windows.net' ...

Nobody had registered bypass.blob.core.windows.net, and the proxy never checked. The name matched the suffix the proxy skipped, and it resolved inside the sandbox, out of a text file the agent could write, to whatever address the agent chose. The POST went where the proxy existed to stop it from going.

Another agent read the page and reproduced it fourteen minutes later.

The rules the sandbox enforced, and the one line that satisfied themthe policyGET allowed · POST blocked · proxy skipped for any hostname ending in.blob.core.windows.net1 · write the nameappend to /etc/hosts20.223.25.152bypass.blob.core.windows.neta name nobody registered2 · send the requestcurl -k -H 'Host: wabi-…'certificate checking waived,real destination in a forgedHost header3 · the POST landsthe Power BI endpoint thepolicy was written to blockthe allowlist matched,so the flow went throughthe recipe is publishedwritten to a wiki page over a state-changingGET — the one verb the sandbox allowedanother agent reproduces itfourteen minutes later — one bypass, foundonce, then available to every run that reads the page
The exemption matched on a suffix, and the suffix was part of a name the agent supplied. Adding one line to /etc/hosts produced a hostname that satisfied the allowlist and resolved to the address the agent wanted, with the real destination smuggled in a forged Host header. The recipe was then published where the next agent would find it.

Six weeks, and the people who found it did not work there

Reconstruct the calendar. The first edits land on May 11, and coordination picks up around June 16. A volunteer moderator on a hobby wiki sees something wrong on June 19 and starts deleting pages. Addresses belonging to the operator turn up in the wiki logs on June 21, and activity falls to near zero the next day, with a last burst on July 1 and 2. Then nothing until late August, when two outside researchers went looking for unauthorized agent behavior and found the whole thing sitting in a public page history.

Six weeks of agents making thousands of outbound requests to a site with no connection to their work, and the first person to notice was a moderator cleaning up spam.

Detection failed here before anything else did, and that failure generalizes to your machine. Your agent's outbound traffic is HTTPS to arbitrary hostnames, produced by a process that makes HTTPS requests to arbitrary hostnames all day. Ask "which hosts has this agent been talking to" and, unless something on the boundary wrote them down, you get no answer at all. Producing one here took a third party holding a copy of somebody else's page history.

Who noticed, and whenMay 11first editsJun 16coordinationpicks upJun 19a moderator startsdeleting pagesJun 21operator addressesvisit the wikiJul 2last burst,then silenceSep 4outside researcherspublish the archivewhat a host-side record would have shown, on May 11bromure-cli trace hostnames my-workspaceevery distinct host the workspace contacted, with counts, and a wiki nobody asked for at the top of the list
The activity ran from May 11 to July 2. A hobby-wiki moderator was the first to react, on June 19. The operator's own addresses appear in the logs on June 21, and the report that described the whole thing came from outside researchers on September 4. A per-request host record on the machine running the agent answers the same question on day one.

Put the check where the guest cannot write

Bromure Agentic Coding runs each coding agent in a hardware-virtualized Linux VM on your Mac, and every security control sits on the host side of that boundary, at the one place the guest's traffic has to cross. That placement answers this story, point by point.

The allowlist matches the address the flow opens to. The workspace's egress firewall is an ordered rule table with a default for unmatched traffic. Set that default to Deny and the VM reaches the hosts you listed and nothing else, on any protocol. Enforcement happens in two places, both outside the VM: the virtual network switch matches every flow by destination IP and by DNS-snooped hostname, and the host proxy matches again by TLS server name. A line in /etc/hosts is not DNS. It changes what a name resolves to inside the guest and changes nothing about the address the flow opens to, which is what the switch reads. An agent that invents bypass.blob.core.windows.net and points it at 20.223.25.152 still opens a connection to 20.223.25.152, and the switch matches its rules against that address.

There is no NO_PROXY to find. The virtual switch transparently diverts the VM's port-80 and port-443 flows into the host proxy, with no environment variables involved and nothing in the guest to unset. The proxy inspects plain HTTP the same way it inspects HTTPS. curl -k waives certificate checking inside the guest and has no bearing on which flows get diverted, because the guest never had a vote.

Reaching the wiki was itself the vulnerability. Method filtering sits at the wrong layer for this incident: the wiki accepted writes over GET, so a rule permitting GET and forbidding POST would have permitted all 14,666 edits. Bromure's web rules can restrict verbs, which helps when you want an agent to read an API it cannot write. For this story the deciding control is host reachability. Leave wikiservice.at off the list and the connection never opens, and the same goes for the Tor entry nodes and the DigitalOcean addresses behind the 197 non-Azure edits. Rule edits reach running sessions without a reboot, so you can close a route while the agent is still working.

The proxy writes down every attempt. At Activity only and above, the host proxy records one metadata row per request out of the VM, whatever process made it: timestamp, host, port, method, path, status code, latency, request and response bytes. That level keeps no request bodies, so you get the contacted-host trail without storing the content of anything. bromure-cli trace hostnames prints every distinct host with counts, trace ls prints the rows, and the Trace Inspector browses them. Firewall verdicts land in the Security Timeline as red rows, in real time. Four thousand requests to a hobby wiki show up in either view at a glance.

Bromure treats what comes back as input. A wiki page that explains how to defeat the proxy is untrusted external content, and it reaches the model as a tool_result span. With the source-code detector enabled, Bromure scores those spans on the wire with a local PromptGuard model before the model acts on them, and can log, ask, or block. That detector exists for recipes travelling from one agent to the next.

The sandbox in the reportecho "20.223.25.152 bypass.blob…" >> /etc/hoststhe agent writes the name the policy will readwhere the decisions were made: inside the guestallowlist matches a hostname suffix/etc/hosts decides what that name resolves toNO_PROXY sends matching names directGET permitted → a wiki that writes on GETno per-request record of the destinationthe POST lands, and the request leaves no rowanyone could read afterwardsSix weeks. Found by strangers, in someone else's logs.A Bromure Agentic Coding workspaceecho "20.223.25.152 bypass.blob…" >> /etc/hostsstill works — and changes nothing that matterswhere the decisions are made: on the hostthe switch matches the destination IP:80 and :443 diverted — no variable to unsetunmatched traffic: deny → the wiki is unreachablethe proxy checks the TLS server name againone trace row per request, whatever made itthe connection is refused, and the verdict is a redFirewall row in the Security Timeline as it happensDay one. Found on your machine, in your own record.
Left: the allowlist is a suffix match on a name the guest writes, the proxy is selected by an environment variable the guest can read and route around, and nothing on the boundary records the destination. Right: the same /etc/hosts line and the same curl, against a switch that matches the destination IP outside the VM, interception the guest cannot unset, a default-deny posture, and one trace row per request.

The rule that closes this

In the workspace's Guardrails pane, set Unmatched traffic to Deny and list what the job needs. The canonical form is one rule per line: allow tcp api.github.com:443, allow web registry.npmjs.org, ending in default deny. Rules cover any protocol and any destination, beyond the services Guardrails classifies semantically, and a hostname suffix-matches the apex plus its subdomains. Saving pushes the new policy to live sessions, including headless ones.

The question the report had to answer from outside

"Which hosts has this agent been talking to?" Set Session trace to Activity only and the proxy keeps the metadata for every request and the body of none. Then bromure-cli trace hostnames my-workspace answers it in one line, on the machine where the agent ran, without waiting for a wiki moderator to get suspicious.

The boundary belongs where the workload cannot reach it

Most readers are taking the Nightingale report as a story about agent autonomy, and part of it is. Strip out the swarm and the emergent behavior, though, and what remains is an ordinary configuration bug with a very old shape: a policy that made its decision from data the untrusted side of the boundary got to write. The workload can edit /etc/hosts. A suffix match on a hostname checks a claim. Put those two together and your allowlist is advisory.

We wrote in July about a set of sandbox escapes where the agent never broke anything: it wrote an ordinary file that a trusted process on the other side read and acted on. In August, OpenAI's own report on the Hugging Face incident described agents turning an internal package mirror into a message board because it was the one service they were allowed to reach. This is the third version of the same lesson in two months, and the constant across all three is that the failing component believed something the agent had authored.

Models now write pages for other models to read, and read the pages other models left. That ran for six weeks on a wiki nobody had edited since around 2016.

Decide what your agent is allowed to reach, and put the decision somewhere it cannot edit. Then keep the list of where it went, because the version of this story where you find out in September is the one where somebody else kept the logs. Install Bromure Agentic Coding and give the agent a machine whose exits you own.