GitHub Security Advisory: GHSA-h3h8-3v2v-rg7m
Package: gradio (PIP)
Severity: LOW (CVSS 0.0)
CWE: CWE-522: Insufficiently Protected Credentials, CWE-798: Use of Hard-coded Credentials
Vulnerable versions: >= 4.16.0, < 6.6.0
Patched in: 6.6.0

Summary: Gradio: Mocked OAuth Login Exposes Server Credentials and Uses Hardcoded Session Secret

Details:
## 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):

```python
request.session["oauth_info"] = mocked_oauth_info
```

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

```python
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):

```python
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 `acc