# SSRF via unvalidated token and webhook URLs

Outbound requests to user-supplied token, webhook, or refresh URLs lacking scheme, host, and IP-pinning checks

_Area: Web infrastructure and server-side requests. Seen in 3 disclosed reports; latest 2026-10-08. Updated 2026-10-24._

## How it works
Code accepts a URL from an untrusted source (request body, stored credential, or an uploaded spec) and later makes an outbound request to it. The trust assumption breaks because the URL is treated as configuration rather than attacker input. Even when a validator exists, it often checks the hostname at validation time while the HTTP client resolves DNS again at connect time, so the checked address and the connected address differ.

## Where it shows up
- Fields named `tokenUrl`, `accessTokenUrl`, `webhook_url`, `refresh_url`, `callback` taken from request bodies or uploaded documents.
- Validators calling `gethostbyname`/`urlparse` separately from the client (`httpx`, `axios`, `requests`) that performs the fetch.
- Auth middleware gated on an env var, leaving endpoints open by default [E1](https://bugbountylibrary.com/r/ghsa%3AGHSA-4w49-gwv8-fpjg.md).
- URL validation applied at some sinks (discovery, tool invocation) but skipped at auth/credential sinks [E2](https://bugbountylibrary.com/r/ghsa%3AGHSA-8cp3-qxj6-px34.md).
- Public auth whitelists matched with `startsWith` on a path prefix, exposing parameterized sub-routes [E3](https://bugbountylibrary.com/r/ghsa%3AGHSA-r745-8hwv-h473.md).
- Credential secrets (`client_secret`, `refresh_token`) placed in the body of a request to a user-controlled host [E3](https://bugbountylibrary.com/r/ghsa%3AGHSA-r745-8hwv-h473.md).

## How to review
For every outbound request, trace the URL back to its source. The invariant: any URL derived from untrusted input must pass scheme allowlisting, host/IP denylisting, and IP pinning so the validated address is the connected address. Confirm the same validation wrapper is used at every sink, not just some. Check that auth middleware is on by default and that whitelist matching is exact, not prefix-based. Verify secrets are never sent to a host the user controls.

## Variants seen
- Unauthenticated endpoint plus bypassable webhook guard via DNS rebinding [E1](https://bugbountylibrary.com/r/ghsa%3AGHSA-4w49-gwv8-fpjg.md).
- OAuth2 `tokenUrl` copied from an untrusted OpenAPI spec, skipping the library's own `ensure_secure_url()` [E2](https://bugbountylibrary.com/r/ghsa%3AGHSA-8cp3-qxj6-px34.md).
- Unauthenticated refresh route POSTing secrets to a credential-controlled URL and reflecting the response [E3](https://bugbountylibrary.com/r/ghsa%3AGHSA-r745-8hwv-h473.md).

## Why fixes fail
Validation applied at one sink but not others leaves the auth path open [E2](https://bugbountylibrary.com/r/ghsa%3AGHSA-8cp3-qxj6-px34.md). Checking DNS at validation time without pinning fails against rebinding [E1](https://bugbountylibrary.com/r/ghsa%3AGHSA-4w49-gwv8-fpjg.md). Prefix-based whitelists silently expose sub-routes [E3](https://bugbountylibrary.com/r/ghsa%3AGHSA-r745-8hwv-h473.md).

## Impact and severity
Blind internal reachability [E1](https://bugbountylibrary.com/r/ghsa%3AGHSA-4w49-gwv8-fpjg.md), credential theft and SSRF [E2](https://bugbountylibrary.com/r/ghsa%3AGHSA-8cp3-qxj6-px34.md), and non-blind SSRF with secret exfiltration [E3](https://bugbountylibrary.com/r/ghsa%3AGHSA-r745-8hwv-h473.md). Severity rises with unauthenticated access, reflected responses, and secrets in the request body.

## Evidence
- E1: https://bugbountylibrary.com/r/ghsa%3AGHSA-4w49-gwv8-fpjg.md
- E2: https://bugbountylibrary.com/r/ghsa%3AGHSA-8cp3-qxj6-px34.md
- E3: https://bugbountylibrary.com/r/ghsa%3AGHSA-r745-8hwv-h473.md
