# URL normalization blocklist bypass

Blocklist or allowlist matches raw URL string before normalization; trailing dots or dot-segments evade it

_Area: Web business logic and race conditions. Seen in 3 disclosed reports; one confirmed bounty: $1,000; latest 2024-03-20. Updated 2026-10-23._

## How it works
A security control compares a URL (or domain) as a raw string against a block/allow list, but the component that later fetches, renders, or delivers it parses the string differently. Any variant the consumer resolves to the blocked target but the matcher sees as a different string slips through. The broken assumption: "string equality means same destination." It does not, because normalization (host trailing dots, `..` segments, userinfo, backslashes, Unicode case-folding) happens after the check.

## Where it shows up
- Blocklist/allowlist implemented as substring or exact match on the unparsed URL [E1](https://bugbountylibrary.com/r/h1%3A422279.md)[E2](https://bugbountylibrary.com/r/h1%3A1102764.md)
- Host checks that permit userinfo (`user@host`) or backslashes before `@` [E1](https://bugbountylibrary.com/r/h1%3A422279.md)
- Link-preview, fetch, or redirect features that match a stored blocklist entry [E2](https://bugbountylibrary.com/r/h1%3A1102764.md)
- Email/domain deny-lists matched on raw strings without Unicode normalization or case-folding [E3](https://bugbountylibrary.com/r/h1%3A2033005.md)
- Any control where the matcher and the consumer are different code paths

## How to review
Find the matcher and the consumer; confirm both operate on the same canonical form. The invariant: the URL/domain must be parsed and normalized (lowercase, resolve dot-segments, strip trailing dot, reject or strip userinfo, apply Unicode normalization/case-folding) *before* comparison, and the normalized value must be what is fetched or delivered. Reject inputs that fail to parse rather than falling through. For domains, compare punycode/IDNA-normalized forms [E3](https://bugbountylibrary.com/r/h1%3A2033005.md). For URLs, compare scheme, host, and port after canonicalization, not substrings [E1](https://bugbountylibrary.com/r/h1%3A422279.md)[E2](https://bugbountylibrary.com/r/h1%3A1102764.md).

## Variants seen
- Allowlist substring check bypassed via userinfo with backslashes, loading attacker content in an admin modal [E1](https://bugbountylibrary.com/r/h1%3A422279.md)
- Blocklist bypassed via trailing-dot host and `..` path segments, so previews still rendered [E2](https://bugbountylibrary.com/r/h1%3A1102764.md)
- Domain deny-list bypassed via Turkish dotted capital İ, which normalizes to the blocked domain downstream [E3](https://bugbountylibrary.com/r/h1%3A2033005.md)

## Impact and severity
Led to phishing content rendered inside a trusted admin surface [E1](https://bugbountylibrary.com/r/h1%3A422279.md), ineffective admin privacy controls [E2](https://bugbountylibrary.com/r/h1%3A1102764.md), and blocked-domain invitations succeeding [E3](https://bugbountylibrary.com/r/h1%3A2033005.md). Severity rises when the bypassed control is the only barrier (admin context, privacy feature, tenant restriction) and when the consumer auto-fetches or auto-renders, removing a user decision from the loop [E1](https://bugbountylibrary.com/r/h1%3A422279.md).

## Evidence
- E1: https://bugbountylibrary.com/r/h1%3A422279.md
- E2: https://bugbountylibrary.com/r/h1%3A1102764.md
- E3: https://bugbountylibrary.com/r/h1%3A2033005.md
