CVE
CVE-2025-62593
CWE
CWE-94, CWE-352
Affected Surface
- PyPI package `ray` before `2.52.0`
- Developer laptops and workstations running local Ray dashboards on `127.0.0.1:8265` or similar
- Private-network or internet-reachable Ray dashboard and Jobs API deployments that relied on browser-request heuristics instead of real authentication
- Teams upgrading to `2.52.0` but leaving token authentication disabled, which remains the documented default in Ray 2.52.0
The best new package-security story from the last three days is not a malicious upload. It is CISA’s 17 August KEV addition for CVE-2025-62593, an actively exploited browser-to-dashboard RCE path in the PyPI package ray. The bug matters because many teams still treat a local Ray dashboard as “developer only” if it binds to 127.0.0.1 or sits inside a private network. This issue breaks that assumption. A malicious site opened in Firefox or Safari can use DNS rebinding plus a forged User-Agent value to talk to Ray’s job-submission APIs and run attacker-chosen commands on the host.
Ray’s own security docs already say the quiet part out loud: Ray Dashboard, Ray Jobs, and Ray Client provide complete access to the cluster and underlying compute resources, and anybody who can reach those services can execute arbitrary code. CVE-2025-62593 is what happens when that trust model collides with a weak browser-detection heuristic instead of actual authentication.
Affected package and version scope
The package boundary is simple:
| Package | Affected versions | Fixed for this CVE |
|---|---|---|
ray | < 2.52.0 | 2.52.0 |
The exposure boundary is not simple. The highest-risk cases are:
- developer machines running
ray start --headorray.init()with the dashboard exposed on the default8265path - shared AI or data-science environments where the Ray dashboard or Jobs API was reachable from adjacent systems
- clusters where operators believed
localhostbinding or a browser block was enough protection - deployments that upgraded to
2.52.0for the CVE fix but did not turn on token auth afterward
That last bullet deserves special attention. Ray 2.52.0 adds built-in token authentication, but the release notes and docs both say it is disabled by default. So 2.52.0 fixes this specific Firefox and Safari rebinding bypass, but it does not turn an exposed unauthenticated dashboard into a safely internet-facing service.
The vulnerable assumption was one line long
The core defense Ray relied on was not a session check, CSRF token, or dashboard auth layer. It was a heuristic:
def is_browser_request(req: Request) -> bool:
return req.headers["User-Agent"].startswith("Mozilla")
That helper fed both the generic “deny browser requests” decorator and the dashboard middleware guarding job-submission paths:
if (
dashboard_optional_utils.is_browser_request(request)
and request.method in [hdrs.METH_POST, hdrs.METH_PUT]
):
return aiohttp.web.Response(
status=405, text="Method Not Allowed for browser traffic."
)
The bug is not subtle once you read the advisory. Ray assumed browser JavaScript could not change the User-Agent header in a fetch() request. Firefox and Safari do allow that in the relevant request path. That means the “starts with Mozilla” rule was not an access control. It was only a guess about client behavior.
Why DNS rebinding turns a local dashboard into a remote target
The attack chain is worth spelling out because this is where many teams get tripped up:
developer runs local or private-network Ray dashboard
-> developer visits malicious page in Firefox or Safari
-> attacker-controlled origin performs DNS rebinding
-> browser now treats the attacker's origin as the same origin for the rebinding target
-> JavaScript sends POST to /api/jobs or /api/job_agent/jobs/
-> request uses User-Agent: Other instead of Mozilla...
-> Ray accepts job submission and executes attacker-chosen shell code
The advisory’s published proof-of-concept used exactly that shape. The payload simply submitted a Ray job whose entrypoint launched Calculator, but the important part is the request body:
sooFetch('/api/jobs/', {
method: 'POST',
headers: {
'User-Agent': 'Other',
},
body: JSON.stringify({
entrypoint: calculatorCommand,
runtime_env: {},
job_id: null,
metadata: {
job_submission_id: timestamp.toString(),
source: 'nccgroup/singluarity'
}
}),
})
That is why this belongs in application security coverage and not just “AI infrastructure news.” The dangerous boundary is the same one AppSec teams already care about in internal admin panels, CI workers, and package registries: a browser session crosses into an execution API that was never supposed to trust the page making the request.
The fix added Sec-Fetch-* checks, not just a better string comparison
The useful part of the upstream patch is that it shows Ray stopped trusting one header. Commit 70e7c727 introduced a second browser signal:
def has_sec_fetch_headers(req: Request) -> bool:
return any(
h in req.headers
for h in (
"Sec-Fetch-Mode",
"Sec-Fetch-Dest",
"Sec-Fetch-Site",
"Sec-Fetch-User",
)
)
The deny decorator now blocks if those headers are present:
if has_sec_fetch_headers(req):
return Response(
text="Browser requests not allowed",
status=aiohttp.web.HTTPMethodNotAllowed.status_code,
)
And the dashboard middleware changed from:
dashboard_optional_utils.is_browser_request(request)
to:
(
dashboard_optional_utils.is_browser_request(request)
or dashboard_optional_utils.has_sec_fetch_headers(request)
)
That change matters because it shifts the control from “does the header look browser-like?” to “does the request carry browser-only fetch metadata even if the user agent string lies?” The added test coverage makes the intended runtime behavior explicit: a POST with User-Agent: Spurious User Agent plus Sec-Fetch-Site: cross-site must still fail.
localhost was never the same thing as safe
Ray’s own 2.51.1 security guidance already warned that the dashboard and jobs services are developer tools that should only be used with access controls in place. CVE-2025-62593 is a concrete example of why.
Binding to 127.0.0.1 protects you from raw remote TCP access. It does not protect you from a browser acting as a confused deputy once the user loads hostile JavaScript. In practice:
127.0.0.1 only stops direct network clients
browser rebinding turns the victim's own browser into the network client
the job API still sees a reachable HTTP request
That distinction is the whole bug.
KEV changed the urgency, not the root cause
The advisory was public in 2025, but the reason this is a fresh 72-hour story is the KEV addition on 17 August 2026. That moves the issue from “published critical bug with PoC” into “CISA says exploitation is active in the wild.”
One detail from the third-party reference attached to NVD is worth reading carefully. Bitsight’s RondoDox analysis says the botnet operators were already attempting CVE-2025-62593 before the CVE publication date, but the specific observed exploit sample still used a Mozilla-prefixed User-Agent string and would therefore hit Ray’s original 405 branch as written. That nuance is useful for defenders:
- do not assume every observed scanner or botnet payload equals successful job execution
- do assume public exploitation interest is real, because CISA KEV says it is and the exploit path is not theoretical
- do not confuse “the sample I saw was buggy” with “the control was strong”
The published advisory itself already showed the working bypass: use a non-Mozilla User-Agent and let DNS rebinding handle the network path.
Ray 2.52.0 fixes this CVE, but you should still enable auth
The public version data for ray adds an important operational wrinkle. The same release that fixed CVE-2025-62593 also introduced token auth as a new feature, and the docs are explicit that:
- token auth is available in
2.52.0+ - it is disabled by default
- Ray expects
RAY_AUTH_MODE=tokenif you want the dashboard, CLI, API clients, and internal services authenticated - the token lives at
~/.ray/auth_tokenby default unless you override it
So the correct fix is not just “upgrade to 2.52.0 and move on.” It is:
upgrade to 2.52.0 or later
-> enable token auth
-> keep the dashboard behind SSH tunneling, VPN, TLS proxy, or equivalent network controls
If you skip the second and third steps, you have removed the Firefox and Safari browser-bypass described by CVE-2025-62593, but you are still trusting a powerful execution surface more than Ray’s own security model recommends.
What to check in your environment
Start with version, exposed ports, and auth mode:
python3 -c "import ray; print(ray.__version__)"
pip show ray
ss -ltnp | rg ':8265|:52365'
rg -n \
"RAY_AUTH_MODE|RAY_AUTH_TOKEN|RAY_AUTH_TOKEN_PATH|ray start|dashboard-host|ray dashboard" \
/etc "$HOME" .github docker-compose*.yml k8s/ helm/ .env .
Then answer the boundary questions that decide real risk:
Can a developer workstation run Ray while browsing the web?
Can any private-network host reach the Ray dashboard or job agent?
Is token auth actually enabled, or only available in the installed version?
Are operators assuming localhost binding is enough?
If you are already on 2.52.0+, validate the auth side too:
export RAY_AUTH_MODE=token
ray get-auth-token --generate
ls -l ~/.ray/auth_token
For remote clusters, the docs are clear that all nodes need the same token and clients need that token configured too. Treat this like real service auth, not like a feature flag you can postpone indefinitely.
Remediation
For teams using Ray in development or internal AI infrastructure, the response should be:
- upgrade
rayto2.52.0or later immediately - enable
RAY_AUTH_MODE=tokenand distribute the token correctly to every node and client - keep dashboard and jobs endpoints behind SSH tunneling, VPN, TLS termination, or another controlled network path
- review whether any developer machines or private-network clusters exposed
8265or job-agent paths during the vulnerable window - treat “developer tools” as privileged execution surfaces, because in Ray they are
The important lesson from CVE-2025-62593 is not just that Firefox and Safari handled fetch() differently than Ray expected. It is that Ray’s dashboard and Jobs API were already powerful enough to run arbitrary code. Once the browser-side guard fell apart, the gap between “internal tool” and “remote execution API” disappeared very quickly.
From research to remediation
Check whether this pattern exists in your codebase
Turn this research into a remediation workflow. Scan dependencies and package manifests for similar supply-chain risk, then prioritize fixes with reachability context.