VYPR
Critical severityOSV Advisory· Published Nov 26, 2025· Updated Apr 15, 2026

CVE-2025-62593

CVE-2025-62593

Description

Ray is an AI compute engine. Prior to version 2.52.0, developers working with Ray as a development tool can be exploited via a critical RCE vulnerability exploitable via Firefox and Safari. This vulnerability is due to an insufficient guard against browser-based attacks, as the current defense uses the User-Agent header starting with the string "Mozilla" as a defense mechanism. This defense is insufficient as the fetch specification allows the User-Agent header to be modified. Combined with a DNS rebinding attack against the browser, and this vulnerability is exploitable against a developer running Ray who inadvertently visits a malicious website, or is served a malicious advertisement (malvertising). This issue has been patched in version 2.52.0.

Affected packages

Versions sourced from the GitHub Security Advisory.

PackageAffected versionsPatched versions
rayPyPI
< 2.52.02.52.0

Affected products

1

Patches

1
70e7c72780bd

Add denial of fetch headers (#58553)

https://github.com/ray-project/rayricho-anyscaleNov 14, 2025via ghsa
3 files changed · +80 2
  • python/ray/dashboard/http_server_head.py+4 1 modified
    @@ -307,7 +307,10 @@ async def browsers_no_post_put_middleware(request, handler):
                 if (
                     # A best effort test for browser traffic. All common browsers
                     # start with Mozilla at the time of writing.
    -                dashboard_optional_utils.is_browser_request(request)
    +                (
    +                    dashboard_optional_utils.is_browser_request(request)
    +                    or dashboard_optional_utils.has_sec_fetch_headers(request)
    +                )
                     and request.method in [hdrs.METH_POST, hdrs.METH_PUT]
                 ):
                     return aiohttp.web.Response(
    
  • python/ray/dashboard/optional_utils.py+18 0 modified
    @@ -138,12 +138,30 @@ def is_browser_request(req: Request) -> bool:
         return req.headers["User-Agent"].startswith("Mozilla")
     
     
    +def has_sec_fetch_headers(req: Request) -> bool:
    +    """Checks for the existance of any of the sec-fetch-* headers"""
    +    return any(
    +        h in req.headers
    +        for h in (
    +            "Sec-Fetch-Mode",
    +            "Sec-Fetch-Dest",
    +            "Sec-Fetch-Site",
    +            "Sec-Fetch-User",
    +        )
    +    )
    +
    +
     def deny_browser_requests() -> Callable:
         """Reject any requests that appear to be made by a browser"""
     
         def decorator_factory(f: Callable) -> Callable:
             @functools.wraps(f)
             async def decorator(self, req: Request):
    +            if has_sec_fetch_headers(req):
    +                return Response(
    +                    text="Browser requests not allowed",
    +                    status=aiohttp.web.HTTPMethodNotAllowed.status_code,
    +                )
                 if is_browser_request(req):
                     return Response(
                         text="Browser requests not allowed",
    
  • python/ray/dashboard/tests/test_dashboard.py+58 1 modified
    @@ -419,10 +419,16 @@ def test_browser_no_post_no_put(enable_test_module, ray_start_with_dashboard):
         webui_url = ray_start_with_dashboard["webui_url"]
         webui_url = format_web_url(webui_url)
     
    +    def dashboard_available():
    +        try:
    +            return requests.get(webui_url).status_code == 200
    +        except Exception:
    +            return False
    +
         timeout_seconds = 30
         start_time = time.time()
    +    wait_for_condition(dashboard_available)
         while True:
    -        time.sleep(3)
             try:
                 # Starting and getting jobs should be fine from API clients
                 response = requests.post(
    @@ -458,6 +464,57 @@ def test_browser_no_post_no_put(enable_test_module, ray_start_with_dashboard):
                     raise Exception("Timed out while testing.")
     
     
    +@pytest.mark.skipif(
    +    os.environ.get("RAY_MINIMAL") == "1",
    +    reason="This test is not supposed to work for minimal installation.",
    +)
    +def test_deny_fetch_requests(enable_test_module, ray_start_with_dashboard):
    +    assert wait_until_server_available(ray_start_with_dashboard["webui_url"]) is True
    +    webui_url = ray_start_with_dashboard["webui_url"]
    +    webui_url = format_web_url(webui_url)
    +
    +    def dashboard_available():
    +        try:
    +            return requests.get(webui_url).status_code == 200
    +        except Exception:
    +            return False
    +
    +    timeout_seconds = 30
    +    start_time = time.time()
    +    wait_for_condition(dashboard_available)
    +    while True:
    +        try:
    +            # Starting and getting jobs should be fine from API clients
    +            response = requests.post(
    +                webui_url + "/api/jobs/", json={"entrypoint": "ls"}
    +            )
    +            response.raise_for_status()
    +            response = requests.get(webui_url + "/api/jobs/")
    +            response.raise_for_status()
    +
    +            # Starting job should be blocked for browsers
    +            response = requests.post(
    +                webui_url + "/api/jobs/",
    +                json={"entrypoint": "ls"},
    +                headers={
    +                    "User-Agent": ("Spurious User Agent"),
    +                    "Sec-Fetch-Site": ("cross-site"),
    +                },
    +            )
    +            with pytest.raises(HTTPError):
    +                response.raise_for_status()
    +
    +            # Getting jobs should be fine for browsers
    +            response = requests.get(webui_url + "/api/jobs/")
    +            response.raise_for_status()
    +            break
    +        except (AssertionError, requests.exceptions.ConnectionError) as e:
    +            logger.info("Retry because of %s", e)
    +        finally:
    +            if time.time() > start_time + timeout_seconds:
    +                raise Exception("Timed out while testing.")
    +
    +
     @pytest.mark.skipif(
         os.environ.get("RAY_MINIMAL") == "1",
         reason="This test is not supposed to work for minimal installation.",
    

Vulnerability mechanics

Generated by null/stub on May 9, 2026. Inputs: CWE entries + fix-commit diffs from this CVE's patches. Citations validated against bundle.

References

9

News mentions

0

No linked articles in our index yet.