# SSRF allowlist regex bypass

Guard and fetcher disagree on the host; unanchored regex, parser differential, or incomplete IP ranges let internal hosts through

_Area: Web infrastructure and server-side requests. Seen in 4 disclosed reports; 2 confirmed bounties: $500 and $7,500; latest 2026-08-28. Updated 2026-10-23._

## How it works
An SSRF guard decides a URL is safe by matching the host or IP against a pattern. The assumption that breaks: the string the guard inspects is the same string the HTTP client will actually connect to. When the check is an unanchored regex, a substring match, or a classification call that parses addresses differently from the client, attacker-controlled text passes the guard while the client resolves it to an internal or attacker host. The guard and the fetcher disagree about what the host is.

## Where it shows up
- Host allowlists written as `/github\.com|.../` or `host.includes(...)` without `^...$` anchoring [E1](https://bugbountylibrary.com/r/blog%3Ablog-tyage-net-posts-2021-06-27-dependabot-rce.md)
- IP deny-lists built on a library classifier (`iptype()`, `netip.IsPrivate`) rather than explicit range enumeration [E2](https://bugbountylibrary.com/r/blog%3Aagarri-fr-blog-archives-2014-10-15-bypassing-blacklists-based-on-ipy-index-html.md)[E3](https://bugbountylibrary.com/r/ghsa%3AGHSA-w98g-5w9p-p3rc.md)
- Guards that only parse IP literals and never resolve DNS, so `127.0.0.1.nip.io` falls through [E4](https://bugbountylibrary.com/r/ghsa%3AGHSA-x44h-65qv-cw74.md)
- Multiple URL-fetching modules in one codebase with divergent guards [E4](https://bugbountylibrary.com/r/ghsa%3AGHSA-x44h-65qv-cw74.md)

## How to review
Find every place a URL/host is validated before a fetch, then confirm the invariant: the exact host the HTTP client will connect to is what was validated, after full normalization and DNS resolution. Check anchoring (`^`/`$` or full-match), reject userinfo and fragment characters in host position, resolve hostnames and re-check every returned IP against an explicit enumerated range list (RFC1918, loopback, link-local, CGNAT 100.64/10, 6to4 2002::/16, NAT64 64:ff9b::/96, fec0::/10), and re-validate after redirects. Note that E2/E3 are classifier-coverage gaps, not regex bugs: the same invariant applies, but the failure is a missing range or a parse mismatch, not a loose pattern.

## Variants seen
- Unanchored host regex accepting `github.com.evil.tld` [E1](https://bugbountylibrary.com/r/blog%3Ablog-tyage-net-posts-2021-06-27-dependabot-rce.md)
- Parser differential: octal IPs classified PUBLIC by IPy but resolved to loopback by urllib2 [E2](https://bugbountylibrary.com/r/blog%3Aagarri-fr-blog-archives-2014-10-15-bypassing-blacklists-based-on-ipy-index-html.md)
- Incomplete IP range coverage in Go netip helpers (CGNAT, 6to4, NAT64, site-local) [E3](https://bugbountylibrary.com/r/ghsa%3AGHSA-w98g-5w9p-p3rc.md)
- No DNS resolution; hostname aliases bypass literal checks [E4](https://bugbountylibrary.com/r/ghsa%3AGHSA-x44h-65qv-cw74.md)

## Why fixes fail
Fixes applied to one fetch module were never ported to a sibling module [E4](https://bugbountylibrary.com/r/ghsa%3AGHSA-x44h-65qv-cw74.md).

## Impact and severity
Ranged from internal service and IMDS access [E3](https://bugbountylibrary.com/r/ghsa%3AGHSA-w98g-5w9p-p3rc.md)[E4](https://bugbountylibrary.com/r/ghsa%3AGHSA-x44h-65qv-cw74.md) to token theft enabling broad repo write access [E1](https://bugbountylibrary.com/r/blog%3Ablog-tyage-net-posts-2021-06-27-dependabot-rce.md). Severity rises with forwarded credentials, reachable metadata endpoints, and chained redirects.

## Evidence
- E1: https://bugbountylibrary.com/r/blog%3Ablog-tyage-net-posts-2021-06-27-dependabot-rce.md
- E2: https://bugbountylibrary.com/r/blog%3Aagarri-fr-blog-archives-2014-10-15-bypassing-blacklists-based-on-ipy-index-html.md
- E3: https://bugbountylibrary.com/r/ghsa%3AGHSA-w98g-5w9p-p3rc.md
- E4: https://bugbountylibrary.com/r/ghsa%3AGHSA-x44h-65qv-cw74.md
