The web page called localhost
On August 17, CISA added Ray's CVE-2025-62593 to its Known Exploited Vulnerabilities catalog. It is remote code execution on a developer's own machine, delivered by a web page the developer visited, reaching a service that was only ever listening on 127.0.0.1. Coding agents have been filling that interface with unauthenticated servers all year. Bromure Agentic Coding puts them somewhere a browser tab cannot dial.
Your dev server, your notebook and your local dashboard each bind an
unauthenticated port to 127.0.0.1, on the reasoning that only the person at
the keyboard can reach it. A web page can reach it as well. The browser
policy standing in the way comes apart in about sixty seconds.
On August 17, CISA added one vulnerability to its Known Exploited Vulnerabilities catalog and gave federal agencies until August 20 to deal with it. The Hacker News wrote it up the next morning. The entry is CVE-2025-62593, a code-injection flaw in Ray, the distributed compute framework a large share of the industry uses to train and serve models.
The code runs on a developer's own laptop. Someone had Ray running locally for their work, opened a web page, and the page ran a command for them.
Three ordinary facts, arranged badly
The advisory rates it 9.4 and describes a chain with no exotic step in it.
Ray's Jobs API takes a shell command. That is the product: you POST to
/api/jobs/ with an entrypoint and the cluster runs it. The dashboard listens
on port 8265, and neither it nor the job endpoints require authentication. That
was a deliberate call by the maintainers, whose position is that
security and isolation must be enforced outside the Ray cluster.
You put it somewhere only trusted callers can reach, and a laptop's loopback
interface sounds like such a place.
Ray did carry one guard against browsers. It checked whether the User-Agent
header started with Mozilla and refused those requests, on the assumption that
a browser cannot lie about its own User-Agent. In Firefox and Safari, the
Fetch API lets a page set that header to whatever it likes.
That leaves same-origin policy, the one control standing between a page on the
internet and a service on your machine. DNS rebinding takes it apart. The
attacker serves the page from a domain whose DNS records they control, with a
very short time-to-live. The page loads, then re-resolves
that same domain to 127.0.0.1. The browser keeps treating it as the same
origin, same hostname and same port, while every request now goes to your
machine. The published proof of concept uses
singularity, a rebinding framework
that has been public for years.
Chain them and a malicious page, or a malvertisement on a page you trusted,
posts a job to your Ray instance with a shell command in it. Chrome is
protected by an unrelated bug; Firefox and Safari are not. Avi Lumelsky at
Oligo theorized the User-Agent bypass; Jonathan Leitschuh, then at Socket,
built the rebinding chain and the disclosure. Ray 2.52.0 fixes it and adds
optional token authentication, off by default.
Attackers got there before the advisory did. The RondoDox botnet picked the flaw up two days before public disclosure, and Oligo's ShadowRay 2.0 research tracks a campaign that turns Ray deployments into a self-propagating botnet: compromised clusters scan for more Ray instances and infect them, a cron job pulls fresh payloads from attacker-controlled repositories every fifteen minutes, and the payload mines Monero, opens reverse shells and takes what it finds. Oligo counted more than 230,000 exposed Ray servers, a tenfold increase since their first report in 2024. One compromised cluster gave up 240 GB of source code, models and datasets.
What is listening on your machine
Set Ray aside and count something else: the unauthenticated HTTP servers bound to your loopback interface right now, and how many of them you could name without looking.
Run lsof -iTCP -sTCP:LISTEN -P after a week of work and the list runs longer
than you would guess. Vite on 5173. A FastAPI service under
uvicorn --reload on 8000. Jupyter on 8888, mlflow ui on 5000, a Postgres
container's published port, an MCP server on 3000, a debug port left open by a
crashed test run. None of them ask who you are. Their authors made the
assumption Ray's maintainers made, and it holds up until a tab in another window
starts making requests.
Agents changed the volume. An agent working a ticket starts services as a matter of course. It runs the dev server to check its own change, brings up a database to run the migration, starts the notebook to look at the data, launches the API to hit an endpoint. It does this several times an afternoon, across several workspaces, and it does not clean up, because nothing tells it to. The processes outlive the task. By Thursday the interface carries a dozen services you did not start yourself and could not list from memory, each one binding without authentication because the framework's quickstart says that on localhost you are fine.
Ray's position reads differently on a machine running a coding agent: security and isolation must be enforced outside the thing you started. On a laptop where the agent and the browser share one kernel, “outside” has nowhere to sit.
In a Bromure workspace, 8265 is not on your Mac
Bromure Agentic Coding runs each workspace in its own Ubuntu VM on Apple's Virtualization framework. That is the whole answer to this attack, and it holds without anyone patching anything.
When the agent runs ray start --head, the dashboard binds to 127.0.0.1:8265
inside the VM. Your Mac's loopback interface is a different interface. A rebound
fetch() from Safari on the host resolves evil.example to 127.0.0.1,
connects to macOS loopback, and gets nothing, because the service the page came
looking for is one kernel over. The published chain targets 127.0.0.1, the
address singularity rebinds to and the address the advisory names, and in a
Bromure workspace that address has nothing on it.
The same goes for the second half of the advisory, the part about using a browser as an intermediary to reach Ray instances inside a corporate network that no one put on the internet. Workspaces run in NAT mode by default, and the manual is specific about what that buys: the VMs are reachable from your Mac but are not exposed on your physical LAN, and inbound connections from elsewhere are not possible unless you publish a service on purpose. Publishing is a per-service Cloudflare quick tunnel you start by pressing a button.
And you get the inventory. The workspace dashboard's Listening Ports card
polls ss -tulnpH inside the guest and lists every externally reachable socket
as the VM-IP:port you would connect to, with the name of the process holding
it. The card hides loopback-only sockets, since nothing outside the VM can
reach them. The same list comes out of
bromure-cli vm <id> -L. That list is the census of the agent's leftovers, and
macOS keeps no such list for you.
The hop after the first one
ShadowRay 2.0 is worth reading for what a compromised node does next, because that is the part isolation has to answer for. It scans for other Ray instances and infects them. It installs a cron job that fetches new instructions every fifteen minutes. It reaches for whatever credentials and datasets are on the machine.
Each of those is an outbound connection, and a Bromure workspace matches outbound connections against rules you wrote. The Outbound connections editor in the Guardrails pane holds a pf-style ruleset: allow or deny, by host, IP or CIDR, protocol and port, matched top to bottom with first match winning, plus one control for traffic that matches nothing.
allow web api.github.com:443
allow tcp registry.npmjs.org:443
deny any 10.0.0.0/8
deny any 192.168.0.0/16
default deny
Two layers enforce that one ruleset. The virtual switch evaluates every flow by
destination IP and by the hostnames it learned from snooping the guest's own DNS
answers, so a rule you wrote against a name still matches when the address moves
underneath it. The MITM proxy evaluates the same rules by TLS SNI, and for web
rules by HTTP method, so you can write allow web api.example.com GET,HEAD.
Bromure injects a reset on a denied TCP connection, so the connect fails instead
of hanging.
All of that enforcement runs on your Mac, outside the VM. Transparent
interception is on by default and diverts the guest's HTTP and HTTPS to Bromure
even if something inside unsets HTTP_PROXY and HTTPS_PROXY. An agent that
read the wrong web page, or a payload that rode in with a package, sits on the
wrong side of the rules it would need to edit. Bromure writes every decision to
Window → Security Timeline as a Firewall row: host, port, allowed or
blocked, in one chronological table alongside the credential, supply-chain and
prompt-injection decisions.
The fifteen-minute cron has the same problem in the other direction. A workspace
is three storage layers, and two of them are yours to discard: Erase home…
resets /home/ubuntu to its post-clone state, Reset to base… re-clones the
workspace system disk from the base image, and the base image stays read-only at
runtime. A crontab in the home directory survives neither.
Localhost was a convention
Ray's maintainers were not careless. They wrote a compute framework that runs commands, said so in the docs, and told you to put a boundary around it. The people running it on a laptop took the loopback interface for that boundary, which is the conclusion dev servers, notebooks and local dashboards have invited for twenty years. That was a convention rather than a control. It held while the only things on your machine were things you started yourself, and it stopped holding once browsers got a scripting engine.
An agent widens the gap every afternoon, one npm run dev at a time. Care does
not close it, because care requires knowing what is listening, and the list
changes each time the agent runs a command.
Stop letting the two share an interface. Put the agent's work in its own
machine, where the ports it opens answer to your Mac and to nothing else, where
the reachable sockets are a list you read rather than an assumption you
inherited, and where the connections going out are matched against rules the
workspace cannot edit. A page that rebinds to 127.0.0.1 then finds an empty
interface and takes a refusal.
Sources: CISA, "CISA Adds One Known Exploited Vulnerability to Catalog" (Aug 17, 2026) · The Hacker News, "CISA Flags Actively Exploited Ray Flaw That Can Trigger Browser-Based RCE" (Aug 18, 2026) · GitHub Advisory GHSA-q279-jhrf-cc6v (CVE-2025-62593) · Oligo Security, "ShadowRay 2.0"