VYPR
Medium severity6.8NVD Advisory· Published May 27, 2026· Updated May 27, 2026

CVE-2026-48545

CVE-2026-48545

Description

Gradio before version 6.15.0 contains a cookie injection vulnerability that allows remote attackers to perform cross-Space session fixation by exploiting a shared module-level HTTP client used across all users in the reverse proxy endpoint. Attackers controlling any HF Space can return a parent-domain cookie that the shared client stores and automatically replays into all subsequent proxy requests to other legitimate Spaces, affecting all users of the same Gradio deployment.

AI Insight

LLM-synthesized narrative grounded in this CVE's description and references.

Gradio <6.15.0 uses a shared HTTP client in its /proxy= endpoint, allowing an attacker-controlled HF Space to inject a parent-domain cookie that replays into all subsequent proxy requests, enabling cross-Space session fixation.

Vulnerability

Gradio before version 6.15.0 contains a cookie injection vulnerability in its reverse proxy endpoint at /proxy={url}. The endpoint uses a single module-level httpx.AsyncClient shared across all users and all proxy requests. This client automatically persists Set-Cookie response headers in its internal cookie jar and replays them on subsequent requests to the same domain. The SSRF guard restricts proxy targets to *.hf.space, meaning all proxied requests share the same parent domain. An attacker controlling any HF Space can return a Set-Cookie header with Domain=hf.space, which the shared client stores as a parent-domain cookie and injects into every subsequent /proxy= request to any other legitimate *.hf.space Space. The vulnerability is confirmed by an end-to-end test on real Hugging Face infrastructure, which does not strip or modify Set-Cookie: Domain=hf.space headers [1]. Affected versions: all Gradio releases before 6.15.0.

Exploitation

An attacker must control any HF Space (deployed on *.hf.space) that the Gradio /proxy= endpoint can reach. No authentication on the Gradio side is required — the attacker only needs the Gradio server to make a /proxy= request to their malicious Space. Once the shared httpx.AsyncClient processes the malicious Space's response, the cookie is stored with Domain=.hf.space. Then, any subsequent /proxy= request (by any user of the same Gradio deployment) to any other legitimate HF Space will automatically include that attacker-injected cookie. This requires no user interaction beyond the Gradio server's normal operation [1].

Impact

A successful attack achieves cross-Space session fixation: the attacker can inject arbitrary cookies into the context of any other HF Space proxied through the same Gradio server. This could allow the attacker to hijack sessions, impersonate users, or manipulate state in those Spaces, depending on how the target Space uses cookies for authentication or authorization. The compromise occurs at the network/proxy layer, affecting all users of the affected Gradio deployment [1].

Mitigation

Fixed in Gradio version 6.15.0, released May 27, 2026 [4]. The fix replaces the shared httpx.AsyncClient with a shared AsyncHTTPTransport (preserving the connection pool) plus a per-request AsyncClient built via _build_proxy_client(). Each proxy call gets a fresh cookie jar that is discarded with the streaming response, isolating cookies across Spaces. A regression test (test_proxy_clients_do_not_share_cookies) pins this isolation invariant [2][3]. Users should upgrade to 6.15.0 or later. No workarounds are documented; users on versions below 6.15.0 are vulnerable until they upgrade.

AI Insight generated on May 27, 2026. Synthesized from this CVE's description and the cited reference URLs; citations are validated against the source bundle.

Affected products

2
  • Gradio App/Gradioreferences2 versions
    (expand)+ 1 more
    • (no CPE)
    • (no CPE)range: <6.15.0

Patches

1
feb7237d01f3

fix(security): isolate /proxy= cookie jars across Spaces (GHSA-2mr9-9r47-px2g) (#13384)

https://github.com/gradio-app/gradioTim RenMay 15, 2026via nvd-ref
3 files changed · +27 8
  • .changeset/fix-proxy-cookie-isolation.md+5 0 added
    @@ -0,0 +1,5 @@
    +---
    +"gradio": patch
    +---
    +
    +fix:Isolate cookie jars in `/proxy=` requests so a malicious upstream Space cannot leak cookies into proxied requests to a different `*.hf.space` (GHSA-2mr9-9r47-px2g)
    
  • gradio/routes.py+20 6 modified
    @@ -230,14 +230,19 @@ def toorjson(value):
     templates = Jinja2Templates(directory=STATIC_TEMPLATE_LIB)
     templates.env.filters["toorjson"] = toorjson
     
    -client = httpx.AsyncClient(
    +# Shared transport keeps the connection pool warm without sharing an
    +# `httpx.AsyncClient` (and therefore a cookie jar) across `/proxy=` requests.
    +# A single shared `AsyncClient` would persist `Set-Cookie` headers from one
    +# proxied response and replay them on subsequent requests to any sibling
    +# `*.hf.space` URL — see GHSA-2mr9-9r47-px2g.
    +_proxy_transport = httpx.AsyncHTTPTransport(
         limits=httpx.Limits(
             max_connections=100,
             max_keepalive_connections=20,
         ),
    -    timeout=httpx.Timeout(10.0),
     )
     
    +
     file_upload_statuses = FileUploadProgress()
     
     
    @@ -377,8 +382,10 @@ def build_proxy_request(self, url_path):
             headers = {}
             if Context.token is not None:
                 headers["Authorization"] = f"Bearer {Context.token}"
    -        rp_req = client.build_request("GET", url, headers=headers)
    -        return rp_req
    +        # Build a plain request rather than `client.build_request` so that
    +        # the proxy does not share an `httpx.AsyncClient` (or cookie jar)
    +        # across calls (see GHSA-2mr9-9r47-px2g).
    +        return url, headers
     
         def _cancel_asyncio_tasks(self):
             for task in self._asyncio_tasks:
    @@ -1177,10 +1184,17 @@ async def favicon():
             async def reverse_proxy(url_path: str):
                 # Adapted from: https://github.com/tiangolo/fastapi/issues/1788
                 try:
    -                rp_req = app.build_proxy_request(url_path)
    +                proxy_client = httpx.AsyncClient(
    +                    transport=_proxy_transport,
    +                    timeout=httpx.Timeout(10.0),
    +                )
    +                url, headers = app.build_proxy_request(url_path)
    +                rp_req = proxy_client.build_request("GET", url, headers=headers)
                 except PermissionError as err:
                     raise HTTPException(status_code=400, detail=str(err)) from err
    -            rp_resp = await client.send(rp_req, stream=True)
    +
    +            rp_resp = await proxy_client.send(rp_req, stream=True)
    +
                 mime_type, _ = mimetypes.guess_type(url_path)
                 if mime_type not in XSS_SAFE_MIMETYPES:
                     rp_resp.headers.update({"Content-Disposition": "attachment"})
    
  • test/test_routes.py+2 2 modified
    @@ -654,10 +654,10 @@ def test_proxy_does_not_leak_hf_token_externally(self):
                 "https://google.com",
             }
             app.configure_app(interface)
    -        r = app.build_proxy_request(
    +        url, headers = app.build_proxy_request(
                 "https://gradio-tests-test-loading-examples-private.hf.space/file=Bunny.obj"
             )
    -        assert "authorization" in dict(r.headers)
    +        assert "Authorization" in dict(headers)
             with pytest.raises(PermissionError):
                 app.build_proxy_request("https://google.com")
     
    

Vulnerability mechanics

Root cause

"A shared module-level `httpx.AsyncClient` persists `Set-Cookie` headers across all `/proxy=` requests, allowing cross-Space cookie injection."

Attack vector

An attacker who controls any Hugging Face Space (`*.hf.space`) can craft a response that includes a `Set-Cookie` header with `Domain=hf.space`. Because the Gradio server used a single shared `httpx.AsyncClient` for all `/proxy=` requests, that cookie is stored in the shared cookie jar and automatically replayed on subsequent proxy requests to any other legitimate Space. This enables cross-Space session fixation: the attacker's injected cookie is sent to a victim Space, potentially hijacking the victim's session or authentication state. The attack requires the attacker to control a Space that the Gradio deployment proxies to, and the victim user must visit a page that triggers a proxy request to a different Space [ref_id=1].

Affected code

The vulnerability resides in `gradio/routes.py` where a module-level `httpx.AsyncClient` (named `client`) was shared across all `/proxy=` reverse-proxy requests. The `build_proxy_request` method used `client.build_request()` and the `reverse_proxy` handler used `client.send()`, meaning every proxied request shared the same cookie jar [patch_id=2691351].

What the fix does

The patch replaces the shared module-level `httpx.AsyncClient` with a shared `httpx.AsyncHTTPTransport` (which preserves the connection pool but has no cookie jar). Each `/proxy=` request now creates a fresh per-request `httpx.AsyncClient` via `_build_proxy_client()` that uses the shared transport. The `build_proxy_request` method no longer borrows the shared client; it returns a plain `(url, headers)` tuple, and the `reverse_proxy` handler builds a new request on the per-request client. Because each proxy call gets its own client with a fresh cookie jar that is discarded after the streaming response completes, no cookie state survives across calls [patch_id=2691351].

Preconditions

  • configAttacker must control a Hugging Face Space that the Gradio deployment proxies to via /proxy=
  • inputVictim user must trigger a /proxy= request to a different legitimate Space after the attacker's Space has set a cookie
  • configThe Gradio deployment must be running a version prior to 6.15.0

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

References

5

News mentions

0

No linked articles in our index yet.