Affected versions
Symfony HTML Sanitizer versions >=6.1, <6.4.50, >=7, <7.4.23, >=8, <8.1.11 are affected by this security issue.
The issue has been fixed in Symfony HTML Sanitizer 6.4.50, 7.4.23, 8.1.11.
Description
UrlSanitizer parses URLs with league/uri, which follows RFC 3986. For a special scheme written with no authority slashes, such as http:evil.example, RFC 3986 yields scheme http, host null and path evil.example. The sanitizer then skips the host allow-list check because the host is null, and with allowRelativeLinks(true) it also waives the rejection of hostless URLs, so the URL is kept. Browsers parse the same string per the WHATWG URL standard. There, // is optional when the scheme differs from the scheme of the page. The authority is then evil.example, and navigation goes to http://evil.example/. With the scheme of the page, as https:evil.example on an https page, browsers read a relative path instead. So on an https site, the attacker uses http:.
The guard added for CVE-2026-45066 rejects the one-slash and three-slash spellings but not the zero-slash form:
1
~^(?:https?|ftp|wss?):(/[^/]|///)~i
Proof of concept, with a configuration that allows one host plus relative links, a natural content-management setup:
1 2 3 4 5
$c = (new HtmlSanitizerConfig())->allowSafeElements()
->allowLinkHosts(['trusted.example'])->allowRelativeLinks(true);
(new HtmlSanitizer($c))->sanitize('<a href="https://evil.example/">x</a>'); // <a>x</a>, correct
(new HtmlSanitizer($c))->sanitize('<a href="http:evil.example/login">x</a>'); // unchanged, bypass
Two more spellings of the same disagreement were found while fixing. A scheme-relative URL with three or more slashes, ///evil.example, is a path for RFC 3986 and the authority evil.example for browsers. An empty authority, https://://evil.example/, is an empty host followed by the path //evil.example/ for RFC 3986, so the host allow-list was skipped, and rebuilding the URL produced https:////evil.example/, which browsers read as the authority evil.example. The media policy is affected the same way under allowRelativeMedias(true).
Every form disappears with allowRelativeLinks(false), so the default configuration is not affected. The severity is rated low on that basis: the bypass requires the opt-in combination of a host allow-list and relative URLs.
Resolution
The guard now requires the special schemes http, https, ftp, ws and wss to be followed by exactly //, which closes the zero-, one- and three-slash spellings in a single check, and rejects scheme-relative URLs that start with three or more slashes. After parsing, a URL whose authority is present but empty is rejected as well.
One behaviour change is worth noting for reviewers: a bare https://, and any URL with an empty authority, is now dropped where it previously round-tripped under allowRelativeLinks(true).
The host allow-list is now also applied to the host 0, which the sanitizer treated as a missing host (PHP considers the string falsy) while browsers resolve it to 0.0.0.0: under allowRelativeLinks(true) such a URL was kept without being checked against the allowed hosts.
The patches for this issue are available here for branch 6.4 (and forward-ported to 7.4, 8.1 and 8.2).
Credits
We would like to thank Ilia Alshanetsky for reporting the issue.
We would also like to thank Bruno Dias and Catarina Amorim from Checkmarx for their help getting a CVE assigned.