GitHub Security Advisory: GHSA-g2qj-prgh-4g9r
Package: github.com/nhost/nhost (GO)
Severity: LOW (CVSS 7.5)
CWE: CWE-200: Exposure of Sensitive Information to an Unauthorized Actor, CWE-598: Use of GET Request Method With Sensitive Query Strings
Vulnerable versions: < 0.0.0[PII_REDACTED]3707-294954e0fc3a
Patched in: 0.0.0[PII_REDACTED]3707-294954e0fc3a

Summary: Nhost Leaks Refresh Tokens via URL Query Parameter in OAuth Provider Callback

Details:
# Refresh Token Leaked via URL Query Parameter in OAuth Provider Callback

## Summary

The auth service's OAuth provider callback flow places the refresh token directly into the redirect URL as a query parameter. Refresh tokens in URLs are logged in browser history, server access logs, HTTP Referer headers, and proxy/CDN logs.

Note that the refresh token is one-time use and all of these leak vectors are on owned infrastructure or services integrated by the application developer.

## Affected Component

- **Repository**: `github.com/nhost/nhost`
- **Service**: `services/auth`
- **File**: `services/auth/go/controller/sign_in_provider_callback_get.go`
- **Function**: `signinProviderProviderCallback` (lines 257-261)

## Root Cause

In `sign_in_provider_callback_get.go:257-261`, after successful OAuth sign-in, the refresh token is appended as a URL query parameter:

```go
if session != nil {
    values := redirectTo.Query()
    values.Add("refreshToken", session.RefreshToken)
    redirectTo.RawQuery = values.Encode()
}
```

This results in a redirect like:
```
HTTP/1.1 302 Found
Location: https://myapp.com/callback?refreshToken=a1b2c3d4-e5f6-7890-abcd-ef[PII_REDACTED]
```

## Proof of Concept

### Step 1: Initiate OAuth login
```
GET /signin/provider/github?redirectTo=https://myapp.com/callback
```

### Step 2: Complete OAuth flow with provider

### Step 3: Auth service redirects with token in URL
```
HTTP/1.1 302 Found
Location: https://myapp.com/callback?refreshToken=a1b2c3d4-e5f6-7890-abcd-ef[PII_REDACTED]
```

### Step 4: Token is now visible in owned infrastructure and services:

**Browser History:**
```
# User's browser history now contains the refresh token
```

**HTTP Referer Header:**
```
# If the callback page loads ANY external resource (image, script, etc.):
GET /resource.js HTTP/1.1
Host: cdn.example.com
Referer: https://myapp.com/callback?refreshToken=a1b2c3d4-e5f6-...
# Note: modern browsers default to strict-origin-when-cross-origin policy,
# which strips query parameters from cross-origin Referer headers.
# Additionally, the Referer is only sent to services integrated by the
# application developer (analytics, CDNs, etc.), not arbitrary third parties.
```

**Server Access Logs:**
```
# Reverse proxy, CDN, or load balancer logs on owned infrastructure:
2026-03-08 12:00:00 GET /callback?refreshToken=a1b2c3d4-e5f6-... 200
```

### Step 5: Attacker uses stolen refresh token
```bash
# Exchange stolen refresh token for new access token
curl -X POST https://auth.nhost.run/v1/token \
  -H 'Content-Type: application/json' \
  -d '{"refreshToken": "a1b2c3d4-e5f6-7890-abcd-ef[PII_REDACTED]"}'

# Note: refresh tokens are one-time use, so this only works if the
# legitimate client has not already consumed the token and if the attacker has
# compromised your infrastructure to get access to this information
```

## Impact

1. **Session Hijacking**: Anyone who obtains the token before it is consumed by the legitimate client can generate new access tokens, thoug