# URL scheme check bypass via parsing quirks

Scheme or internal-URL validation defeated by backslashes, line breaks, or normalization differences

_Area: Web injection and code execution. Seen in 3 disclosed reports; 2 confirmed bounties: $300 and $300; latest 2023-06-22. Updated 2026-10-23._

## How it works
A security decision (is this URL internal/relative? is this scheme safe?) is made by one parser, but the URL is later consumed by a different parser or the browser itself. The two disagree on backslashes, line breaks, or normalization, so a URL classified as safe/internal is actually external or carries a dangerous scheme. The invariant "the URL I validated is the URL that gets used" breaks.

## Where it shows up
- Code that adds `rel=noopener`/`noreferrer` or allowlists schemes based on its own URL classification [E2](https://bugbountylibrary.com/r/h1%3A317243.md)[E3](https://bugbountylibrary.com/r/h1%3A306414.md)
- "Restore URL" / session-restore paths that load a stored URL without scheme allowlisting [E1](https://bugbountylibrary.com/r/h1%3A1438028.md)
- Link renderers that decide external vs relative before the browser normalizes the string [E3](https://bugbountylibrary.com/r/h1%3A306414.md)
- Near-identical templates where one copy omits a security header (e.g. `referrer=never`) [E1](https://bugbountylibrary.com/r/h1%3A1438028.md)
- Inputs containing `\`, newlines, `//`, or encoded slashes reaching a URL classifier [E2](https://bugbountylibrary.com/r/h1%3A317243.md)[E3](https://bugbountylibrary.com/r/h1%3A306414.md)

## How to review
Check that the exact string validated is the string consumed, or that validation happens after full normalization by the same rules the consumer uses. For scheme checks, require an explicit allowlist of safe schemes (`http`, `https`, relative) and reject everything else, including `javascript:` and `data:` [E1](https://bugbountylibrary.com/r/h1%3A1438028.md). For link attributes, confirm the external/internal decision uses a parser matching browser behavior, not a regex on raw text [E2](https://bugbountylibrary.com/r/h1%3A317243.md)[E3](https://bugbountylibrary.com/r/h1%3A306414.md). Diff sibling templates for missing security headers [E1](https://bugbountylibrary.com/r/h1%3A1438028.md).

## Variants seen
- Session-restore handler restores a `javascript:` URL with no scheme validation, executing on an internal privileged origin [E1](https://bugbountylibrary.com/r/h1%3A1438028.md)
- `rel=noreferrer` fix skipped when the URL contains a line break, so the attribute is never added [E2](https://bugbountylibrary.com/r/h1%3A317243.md)
- Backslash-prefixed URL (`/\host/...`) treated as relative by the sanitizer but navigated externally by the browser, omitting `rel=noopener` [E3](https://bugbountylibrary.com/r/h1%3A306414.md)

## Why fixes fail
Both Phabricator fixes added protection based on the sanitizer's own URL classification; parser-differential inputs (newline, backslash) fell outside that classification, so the attribute was silently omitted [E2](https://bugbountylibrary.com/r/h1%3A317243.md)[E3](https://bugbountylibrary.com/r/h1%3A306414.md). The fix addressed the common case, not the invariant.

## Impact and severity
Led to XSS on a privileged internal origin [E1](https://bugbountylibrary.com/r/h1%3A1438028.md) and reverse tabnabbing / parent-tab redirection via `window.opener` [E2](https://bugbountylibrary.com/r/h1%3A317243.md)[E3](https://bugbountylibrary.com/r/h1%3A306414.md). Severity rises when the origin is privileged or the leak exposes a secret (uuidKey via Referer) [E1](https://bugbountylibrary.com/r/h1%3A1438028.md). Bounties were modest (median 300) but impact was real.

## Evidence
- E1: https://bugbountylibrary.com/r/h1%3A1438028.md
- E2: https://bugbountylibrary.com/r/h1%3A317243.md
- E3: https://bugbountylibrary.com/r/h1%3A306414.md
