NewYour coding agent can read the release notes before it upgrades.Set up the MCP server →
Packagist · #1508 most downloaded on Packagist
A simple but effective DOM/SVG/MathML Sanitizer for PHP 7.4+
Last release 8 days ago
30 Sep 2026
Ships unpredictably
gaps range from 9 days to 2.1 years
Most releases are documented
notes for 12 of 20 stable releases
Nothing withdrawn
no release was ever pulled
5 years old
20 releases · first in 2021
One column per quarter.
Doctypes with an internal subset are removed whole. The doctype strip stopped at the first > , which inside an internal subset ( <!DOCTYPE svg [<!ENTI
>, which inside an internal subset (<!DOCTYPE svg [<!ENTITY nb " ">]>) is the end of the first entity declaration, so the subset's closing ]> was left behind. In SVG and MathML mode that made parsing fail and sanitize() returned an empty string for any document carrying a subset; in HTML mode the ]> showed up as an extra paragraph of text. Quoted strings in the doctype are now skipped whole, so a ] or > inside an entity value no longer ends the match early either. This was not a sanitizing bypass: entity declarations were always removed. Reported in #7 by @FlorianDuvalSaasOffice.Full changelog: 1.0.18...1.0.19
This release fixes a bypass of the CSS external-resource filter, which could let sanitized HTML or SVG load an attacker-chosen URL through <style> blo
This release fixes a bypass of the CSS external-resource filter, which could let sanitized HTML or SVG load an attacker-chosen URL through <style> blocks, inline style attributes, or SVG presentation attributes:
url() check. Addresses GHSA-94fv-h7hv-365q, reported by NotAFlightRisk./*, quotes, parens or semicolons no longer act as CSS syntax; escaped backslashes are treated as / and tabs are ignored, matching the URL parser; and a raw newline now ends a CSS string, as it does in the browser.Applications sanitizing untrusted HTML or SVG should update to 1.0.18.
Full changelog: 1.0.17...1.0.18
This release fixes two cross-site scripting bypasses affecting applications that render untrusted sanitized content:
This release fixes two cross-site scripting bypasses affecting applications that render untrusted sanitized content:
href, so animations cannot recreate dangerous links after sanitization. Also reject targets involving event handlers, style, and namespace declarations. Normal transform, color, and motion animations remain supported. Addresses GHSA-7x4f-fj83-6xfw.Both issues were reported by Rudloff. Applications rendering untrusted SVG, HTML, or MathML should update to 1.0.17.
Full changelog: 1.0.16...1.0.17
Two bypasses reported against 1.0.15, both closed here.
Two bypasses reported against 1.0.15, both closed here.
Dangerous URL schemes survived in every URL attribute except href. isDangerousUrl() gated on ['href', 'xlink:href'], but the allow-list admits many more URL-valued attributes — action, cite, poster, src, srcset, background — and none of them were scheme-validated. Because form, button/input and type are all allowed by default, a complete submittable form whose action was a javascript: URI round-tripped intact and executed on submit (ref GHSA-mrpv-6x26-mf6c, reported by @0xMoError-22). The scheme check now applies to every URL-bearing attribute, srcset is judged per comma-separated candidate so a scheme cannot hide in a later one, and the post-serialization regex pass covers the same set as a second net.
CSS comments and escapes defeated the url() check in SVG presentation attributes. isExternalUrl() matched the raw attribute value, so fill="url(\2f\2f evil.example/x)" or fill="url(/**/ //evil.example/x)" passed while the browser's CSS tokenizer decoded the escape or dropped the comment and fetched the external resource — the same parser-differential as GHSA-ww22-4mqv-x5w3, on the attributes that fix never covered (ref GHSA-cjfg-j8jp-5xvc, reported by @0xMoError-22). The value is now run through the same CSS normalizer the <style> and style paths use before matching.
On severity: the form-action vector is directly executable stored XSS (CWE-79) and the presentation-attribute vector is an external resource load (CWE-184); both require the consumer to render sanitized output, and the form vector requires a victim submission. Benign values — relative actions, url(#fragment) references, inert data:image/* URLs, multi-candidate srcset — are unaffected, and the suite now carries regression tests for every payload and every preserved case. Suite is green at 129 tests / 247 assertions.
Anyone embedding untrusted SVG or HTML should update. Grav users should take the next Grav release, which bundles this version.
Base64-encoded data: URLs slipped past the href / xlink:href checks (ref GHSA-wcj2-r6vg-rm97 , reported by @0xMoError-22 ).
Base64-encoded data: URLs slipped past the href / xlink:href checks (ref GHSA-wcj2-r6vg-rm97, reported by @0xMoError-22).
The old check rejected javascript: by scheme but judged data: URLs by a payload-content heuristic — the literal substring onload. Because data: payloads are routinely Base64-encoded, the dangerous content (<script>, event handlers) never existed as a literal substring, so data:text/html;base64,… carrying a complete embedded document survived sanitization in both HTML and SVG modes.
data: URLs are now judged by scheme policy, mirroring the javascript: rejection: any data: URL in href / xlink:href is removed unless it declares an inert image MIME type (image/png, image/jpeg, image/gif, image/webp, image/bmp, image/x-icon). Script-capable types — text/html, image/svg+xml, application/xhtml+xml — are rejected regardless of encoding. Legitimate inline image hrefs (base64 PNG icons and the like) keep working, and the post-serialization regex pass gained the same policy as a second net.
On severity: Medium. Current Chrome, Firefox and Safari block top-level navigation to data:text/html URLs, so this was a defense-in-depth gap at the sanitizer's filtering boundary rather than directly executable XSS. Suite is green at 98 tests / 196 assertions.
Anyone embedding untrusted SVG or HTML should update. Grav users should take the next Grav release, which bundles this version.
Two follow-up bypasses in the dangerous-CSS checks added in 1.0.13, both closed here (ref GHSA-ww22-4mqv-x5w3 ).
Two follow-up bypasses in the dangerous-CSS checks added in 1.0.13, both closed here (ref GHSA-ww22-4mqv-x5w3).
A /* inside a string literal ate the rest of the stylesheet. Comment stripping used a plain regex, so content:"/*" was read as the start of a comment; being unterminated, it swallowed everything after it and hid whatever dangerous tokens followed. That made 1.0.13 weaker than 1.0.12 for this shape. Comment stripping is now string-aware: a /* inside a "..." or '...' string is content, not a comment.
image-set() matching could not cross a nested paren. The pattern used [^)]*, so image-set(url(a.png) 1x, "https://evil" 2x) slipped past, as did the same candidate nested inside cross-fade(), and an external URL smuggled through a custom property and pulled back in with var(). The regex is replaced by a single string- and paren-aware pass that flags an off-origin scheme in a quoted string whenever it is an argument, at any depth, to an image function or the value of a custom property.
Relative image-set() candidates, url(#fragment), and a URL shown only via content: are still left alone. Suite is green at 84 tests / 150 assertions.
Anyone embedding untrusted SVG or HTML should update. Grav users should take the next Grav release, which bundles this version.
The CSS checks introduced in 1.0.10 can be bypassed two ways, both closed in this release ( GHSA-ww22-4mqv-x5w3 ).
The CSS checks introduced in 1.0.10 can be bypassed two ways, both closed in this release (GHSA-ww22-4mqv-x5w3).
Comments split the tokens the checks look for. The checks decoded CSS escapes but never removed comments, so a comment dropped inside a token hid it, e.g. u/**/rl(https://host/x) or url(htt/**/ps://host/x). CSS is now normalized before the dangerous-token checks run: comments are stripped, escapes are decoded, then comments are stripped a second time, because decoding can synthesize a comment that was not there on the first pass (\2f\2a decodes to /*).
image-set() never matched any pattern. It loads an external resource without ever writing url(), so none of the existing patterns applied — and unlike the comment payloads, browsers do fetch it. Both image-set() and -webkit-image-set() are now covered, while relative references still pass.
Whitespace is deliberately left alone, since it is equally inert to browsers and collapsing it would risk rejecting legitimate multi-line CSS. Adds 9 regression cases; suite is green at 80 tests / 144 assertions.
Anyone embedding untrusted SVG or HTML should update. Grav users should take the next Grav release, which bundles this version.
Inline style=" attributes are now checked with the same CSS rules already applied to <style>` element text ( GHSA-jfrr-ch68-f2w9 ). Reported by @Asadb
Inline style=" attributes are now checked with the same CSS rules already applied to <style>` element text (GHSA-jfrr-ch68-f2w9). Reported by @Asadbeknur.
Since 1.0.10, <style> element text has been normalized for CSS escapes and checked for @import, expression() and external or data: url() values. Inline style attributes were only matched against a pattern that required a quote after url(, even though CSS makes those quotes optional. The same payload was therefore rejected inside a <style> block and accepted as an attribute.
Closed in this release:
url(//host/x) and url(https://host/x) without quotes — the reported vectorurl("data:..."), which slipped through even when quotedurl (…) with whitespace before the parenthesis, URL(…) in uppercase, and a newline inside url()url(\\68 ttps://host/x) and url(\\2f\\2f host/x)@import and expression() in an attribute valueSame-document references like url(#gradient) and ordinary declarations are unaffected, and are covered by new preservation tests.
Anyone embedding untrusted SVG or HTML should update. Grav users should take the next Grav release, which bundles this version.
Harden loadDocument against XXE and entity-expansion attacks
Harden loadDocument against XXE and entity-expansion attacks
Strip <!DOCTYPE> and <!ENTITY> declarations from sanitizer input before
parsing, then call loadXML/loadHTML with LIBXML_NONET so the parser cannot
make outbound filesystem or network requests for external entities/DTDs.
Also calls libxml_disable_entity_loader on PHP < 8 (no-op on PHP 8+,
where the default already declines external entities).
Closes the dom-sanitizer half of GHSA-3446-6mgw-f79p (filed against Grav,
which uses this library as its SVG sanitizer). The companion fix in Grav
core is in VectorImageMedium's dimension reader.
Two new XXE regression tests added to DomSanitizerTest:
Fixed CSS injection via SVG/HTML <style> text content ( GHSA-93vf-569f-22cq ) — DOMSanitizer::sanitize() allowed <style> elements in both SVG and HTML
<style> text content (GHSA-93vf-569f-22cq) — DOMSanitizer::sanitize() allowed <style> elements in both SVG and HTML mode but never inspected their text content, letting CSS url() references and @import rules pass through unfiltered. An attacker could exfiltrate the page URL to an external host, load arbitrary stylesheets, or use CSS attribute-selector tricks to leak form token values. The fix walks <style> text nodes and drops the element if it contains @import, url(...) with an external scheme (http:, https:, ftp:, //, data:), or legacy expression(). CSS hex escapes like \75 rl(...) are decoded before matching so escape-based bypasses are caught. Fragment references such as url(#gradientId) are preserved so SVG <defs>, gradients, filters, and masks continue to work normally.Fixed SVG sanitizer bypass via ASCII whitespace entities ( #6 ) — Addresses a bypass of the CVE-2026-33172 fix where character entities like (tab), (n…
Merge pull request #1 from DeepDiver1975/fix/removeattributens
Merge pull request #1 from DeepDiver1975/fix/removeattributens
fix: disallow xlink:href
Nothing published for this version
Nothing published for this version
Nothing published for this version
Nothing published for this version
Nothing published for this version
Nothing published for this version
Nothing published for this version
Nothing published for this version
Your coding agent can read these notes before it upgrades. Set up the MCP server →