Symfony's HtmlSanitizer UrlAttributeSanitizer Omits action/formaction/poster/cite — `javascript`: URI Survives Sanitization (XSS)GHSA-hhg7-c65m-h7ff
Fix: symfony/symfony@26a598fGHSA-hhg7-c65m-h7ff is a Cross-site Scripting (XSS) vulnerability in symfony/html-sanitizer. A fix is available for symfony/html-sanitizer — see the affected versions and patch details below.
Exploitation Status
No confirmed exploitation observed yet
- CISA’s own triage has not observed active exploitation or public proof-of-concept code for this CVE as of its last assessment.
Exploitation and automatability from CISA’s SSVC triage for GHSA-hhg7-c65m-h7ff.
EPSS Exploitation Probability
Probability of exploitation in the next 30 days, from FIRST.org EPSS.
Real-World Exposure
symfony/html-sanitizer🐘symfony/html-sanitizer🐘symfony/html-sanitizer🐘symfony/symfony🐘symfony/symfony🐘symfony/symfonyReal-time download stats are indexed for npm and PyPI packages. This vulnerability affects Packagist packages — download data is not available via public APIs for these ecosystems.
Description
Description
symfony/html-sanitizer lets applications sanitise untrusted HTML. UrlAttributeSanitizer is the visitor responsible for validating URL-valued attributes and stripping dangerous schemes from them; it runs on every element regardless of configuration. Whether an attribute is kept is decided by the element/attribute allow-list; validating the scheme of a URL attribute is solely UrlAttributeSanitizer's responsibility.
UrlAttributeSanitizer::getSupportedAttributes() returned only ['src', 'href', 'lowsrc', 'background', 'ping']. The HTML URL-valued attributes action (<form>), formaction (<button>, <input type=image>), poster (<video>) and cite (<blockquote>, <q>, <del>, <ins>) were missing from that list, so DomVisitor never invoked scheme validation for them. As a result, when a configuration admits one of those attributes, a javascript: URI in it survived sanitisation.
Conditions for exploitation
allowSafeElements() is not affected: <form> and the formaction attribute are both flagged unsafe in W3CReference, and allowElement('form') resets the element's attribute list. Reaching the vulnerable attributes requires a deliberately permissive configuration, for example:
<form>+action:allowElement('form', '*'),allowElement('form', ['action', …]),allowElement('form')->allowAttribute('action', 'form'), or theallowStaticElements()preset (whose docblock already warns the output "may still contain other dangerous behaviors");<button>/<input type=image>+formaction:allowElement(…, '*'),allowAttribute('formaction', …), orallowStaticElements();<blockquote>/<q>/<del>/<ins>+cite, or<video>+poster: similarly via'*',allowAttribute(), orallowStaticElements().
For the action / formaction cases the victim must additionally submit the form or click the button.
Resolution
UrlAttributeSanitizer now also handles action, formaction, cite and poster. action / formaction / cite are validated against the link schemes (like <a href>, so javascript: is rejected and data: is dropped too); poster is validated against the media schemes (so data: images keep working). The behaviour of <a href> and <img src> is unchanged.
One behaviour change to be aware of: a relative action="/submit" on an allowed <form> is now dropped by default (the same as <a href> / <img src> today); ->allowRelativeLinks() re-enables it.
The patch for this issue is available here for branch 6.4.
Credits
Symfony would like to thank Himanshu Anand and Rémi Pelloux for reporting the issue and Nicolas Grekas for providing the fix.
Affected Packages
| Ecosystem | Package | Vulnerable range | Fix |
|---|---|---|---|
| 🐘Packagist | symfony/html-sanitizer | ≥ 6.1.0&&< 6.4.40 | 6.4.40composer require symfony/html-sanitizer:^6.4.40 |
| 🐘Packagist | symfony/html-sanitizer | ≥ 7.0.0&&< 7.4.12 | 7.4.12composer require symfony/html-sanitizer:^7.4.12 |
| 🐘Packagist | symfony/html-sanitizer | ≥ 8.0.0&&< 8.0.12 | 8.0.12composer require symfony/html-sanitizer:^8.0.12 |
| 🐘Packagist | symfony/symfony | ≥ 6.1.0&&< 6.4.40 | 6.4.40composer require symfony/symfony:^6.4.40 |
| 🐘Packagist | symfony/symfony | ≥ 7.0.0&&< 7.4.12 | 7.4.12composer require symfony/symfony:^7.4.12 |
| 🐘Packagist | symfony/symfony | ≥ 8.0.0&&< 8.0.12 | 8.0.12composer require symfony/symfony:^8.0.12 |
Affected Products
symfonysensiolabsDetection & mitigation playbook
Open-source dependencyDetect
Scan your dependency tree (package-lock.json, pnpm-lock.yaml, requirements.txt, go.sum, etc.) for symfony/html-sanitizer, including transitive dependencies — a direct dependency you never call can still pull in a vulnerable version.
Fix
Update symfony/html-sanitizer to 6.4.40 or later, then make sure no transitive (indirect) dependency still pins the vulnerable range — O3 confirms GHSA-hhg7-c65m-h7ff is resolved across your whole dependency graph.
Workarounds
Escape or sanitise the affected output on the server side rather than relying on client-side filtering, and add a Content-Security-Policy that blocks inline script execution so injected markup cannot run even if it reaches the page.
Frequently Asked Questions
Is GHSA-hhg7-c65m-h7ff in your dependencies?
Find it across Packagist, including transitive dependencies.