Severity by source
CVSS:4.0/AV:N/AC:L/AT:N/PR:N/UI:P/VC:N/VI:N/VA:N/SC:L/SI:L/SA:N/E:X/CR:X/IR:X/AR:X/MAV:X/MAC:X/MAT:X/MPR:X/MUI:X/MVC:X/MVI:X/MVA:X/MSC:X/MSI:X/MSA:X/S:X/AU:X/R:X/V:X/RE:X/U:X
Network vector, high complexity (requires decoding downstream consumer), low privileges (content submission), user interaction (victim must view or click spoofed link), limited confidentiality-only impact.
Primary rating from Vendor (https://github.com/symfony/symfony).
CVSS VectorVendor: https://github.com/symfony/symfony
CVSS:4.0/AV:N/AC:L/AT:N/PR:N/UI:P/VC:N/VI:N/VA:N/SC:L/SI:L/SA:N/E:X/CR:X/IR:X/AR:X/MAV:X/MAC:X/MAT:X/MPR:X/MUI:X/MVC:X/MVI:X/MVA:X/MSC:X/MSI:X/MSA:X/S:X/AU:X/R:X/V:X/RE:X/U:X
Lifecycle Timeline
3DescriptionCVE.org
Description
Symfony\Component\HtmlSanitizer\TextSanitizer\UrlSanitizer::parse() rejects URLs containing raw Unicode explicit-direction BiDi formatting characters (U+202A-U+202E, U+2066-U+2069) as a defense against visual-spoofing of the rendered href. The check covers only the raw UTF-8 forms of those code points: the percent-encoded forms (%E2%80%AE for U+202E, %E2%81%A6 for U+2066, etc.) are not matched by the deny regex, survive league/uri's parse/build cycle, and are re-emitted unchanged in the sanitized URL. Any downstream consumer that decodes the link before display - phishing-detection filters that compare urldecode($href) against a domain allow-list, audit-log dashboards that show a decoded form for readability, hover-tooltip previews, federated/syndicated content where the decoder lives on the consuming side - restores the BiDi character and the visual spoof that the original defense was filed to prevent.
The same UrlSanitizer::parse() carries an ASCII-only /\s/ whitespace check (no /u modifier) intended as a backstop against malformed URLs. Without the /u modifier, PCRE's \s matches only ASCII whitespace, so Unicode whitespace characters - NBSP (U+00A0), the zero-width no-break space / BOM (U+FEFF), line/paragraph separators (U+2028, U+2029), ogham space (U+1680), the U+2000-U+200A en/em quad family, narrow / medium / ideographic spaces (U+202F, U+205F, U+3000) and NEL (U+0085) - pass through unchanged in both raw and percent-encoded forms. In hostname positions they enable lookalike spoofs (example<NBSP>.com); in path/query/fragment they enable allow-list drift when a downstream consumer strips whitespace before comparison.
Resolution
UrlSanitizer::parse() now denies BiDi formatting marks together with Unicode whitespace and the zero-width no-break space, in both the raw input and the percent-decoded form of each parsed URL component (user, pass, host, path, query, fragment). ASCII space remains tolerated in path/query/fragment via the existing percent-encoding step.
The patches for this issue are available here for branch 6.4 (and forward-ported to 7.4, 8.0 and 8.1).
Credits
Symfony would like to thank Scott Arciszewski (Trail of Bits) for reporting the issue and Nicolas Grekas for providing the fix.
AnalysisAI
Symfony HtmlSanitizer's UrlSanitizer::parse() method fails to block percent-encoded Unicode BiDi formatting marks (U+202A-U+202E, U+2066-U+2069) and Unicode whitespace characters, allowing both character classes to survive sanitization and be re-emitted unchanged in the output URL. Applications running symfony/html-sanitizer versions 6.1.0 through the patched releases where a downstream consumer - such as a phishing-detection filter, audit dashboard, or federated content pipeline - decodes the href before display or comparison are susceptible to visual-spoofing attacks that defeat the sanitizer's own domain allow-list defenses. No public exploit has been identified at time of analysis and the vulnerability is not listed in CISA KEV, but the bypass technique is fully documented in the advisory and the fix commit's test cases make reproduction straightforward.
Technical ContextAI
The affected code lives in Symfony\Component\HtmlSanitizer\TextSanitizer\UrlSanitizer::parse() within the composer packages pkg:composer/symfony_html-sanitizer and pkg:composer/symfony_symfony (affected ranges >= 6.1.0). The component relies on league/uri for URL parsing and reconstruction, which transparently round-trips percent-encoded sequences without expanding them, so the existing raw-character deny regex - anchored to literal UTF-8 code points - never sees the encoded equivalents; %E2%80%AE (U+202E RIGHT-TO-LEFT OVERRIDE) and %E2%81%A6 (U+2066 LEFT-TO-RIGHT ISOLATE) pass straight through. The second flaw is a missing PHP PCRE /u (Unicode) modifier on the /\s/ whitespace check: without it, PCRE operates in byte mode and matches only ASCII whitespace (0x09-0x0D, 0x20), leaving NBSP (U+00A0), BOM/ZWNBSP (U+FEFF), line/paragraph separators (U+2028, U+2029), ogham space (U+1680), the U+2000-U+200A en/em quad family, and several others unblocked in both raw and percent-encoded forms. CWE-451 (UI Misrepresentation of Critical Information) correctly classifies the root cause: the sanitizer's security property - that no visual-spoofing character survives - is violated because the deny-list check is not applied to decoded URL components.
RemediationAI
Upgrade symfony/html-sanitizer to version 6.4.41, 7.4.13, or 8.0.13 depending on your branch, as detailed in the security advisory at https://github.com/symfony/symfony/security/advisories/GHSA-v3wm-qf9p-c549; the patch commit is https://github.com/symfony/symfony/commit/b21a626fd90f5c12d2db432c629eed3e780ba2f8. Applications that cannot immediately upgrade should add a compensating control at every point where a sanitized URL is decoded before use: apply rawurldecode() to each URL component individually and then reject any value that matches the BiDi or Unicode-whitespace character classes; this mirrors what the fix does in the component itself. The trade-off of the workaround is that it must be implemented and maintained in application code separately from Symfony's own defense layer, and any newly identified bypass character class would require a manual update. Applications that serve the sanitized href verbatim as an HTML attribute without any downstream decoding step are not exposed by this vulnerability and do not require an emergency upgrade, though patching is still recommended as a defense-in-depth measure.
Same technique Information Disclosure
View allShare
External POC / Exploit Code
Leaving vuln.today
EUVD-2026-44373
GHSA-v3wm-qf9p-c549