VYPR
Medium severityOSV Advisory· Published Aug 12, 2025· Updated Apr 15, 2026

CVE-2025-55166

CVE-2025-55166

Description

savg-sanitizer is a PHP SVG/XML sanitizer. Prior to version 0.22.0, the sanitization logic in the cleanXlinkHrefs method only searches for lower-case attribute name, which allows to by-pass the isHrefSafeValue check. As a result this allows cross-site scripting or linking to external domains. This issue has been patched in version 0.22.0.

Affected packages

Versions sourced from the GitHub Security Advisory.

PackageAffected versionsPatched versions
enshrined/svg-sanitizePackagist
< 0.22.00.22.0

Affected products

1

Patches

2
0afa95ea74be

Merge commit from fork

https://github.com/darylldoyle/svg-sanitizerDaryll DoyleAug 12, 2025via ghsa
5 files changed · +62 23
  • src/Sanitizer.php+38 15 modified
    @@ -421,7 +421,7 @@ protected function cleanAttributesOnWhitelist(\DOMElement $element)
                  * Such as xlink:href when the xlink namespace isn't imported.
                  * We have to do this as the link is still ran in this case.
                  */
    -            if (false !== strpos($attrName, 'href')) {
    +            if (false !== stripos($attrName, 'href')) {
                     $href = $element->getAttribute($attrName);
                     if (false === $this->isHrefSafeValue($href)) {
                         $element->removeAttribute($attrName);
    @@ -453,14 +453,17 @@ protected function cleanAttributesOnWhitelist(\DOMElement $element)
          */
         protected function cleanXlinkHrefs(\DOMElement $element)
         {
    -        $xlinks = $element->getAttributeNS('http://www.w3.org/1999/xlink', 'href');
    -        if (false === $this->isHrefSafeValue($xlinks)) {
    -            $element->removeAttributeNS( 'http://www.w3.org/1999/xlink', 'href' );
    -            $this->xmlIssues[] = array(
    -                'message' => 'Suspicious attribute \'href\'',
    -                'line' => $element->getLineNo(),
    -            );
    +        foreach ($element->attributes as $attribute) {
    +            // remove attributes with unexpected namespace prefix, e.g. `XLinK:href` (instead of `xlink:href`)
    +            if ($attribute->prefix === '' && strtolower($attribute->nodeName) === 'xlink:href') {
    +                $element->removeAttribute($attribute->nodeName);
    +                $this->xmlIssues[] = array(
    +                    'message' => sprintf('Unexpected attribute \'%s\'', $attribute->nodeName),
    +                    'line' => $element->getLineNo(),
    +                );
    +            }
             }
    +        $this->cleanHrefAttributes($element, 'xlink');
         }
     
         /**
    @@ -470,13 +473,33 @@ protected function cleanXlinkHrefs(\DOMElement $element)
          */
         protected function cleanHrefs(\DOMElement $element)
         {
    -        $href = $element->getAttribute('href');
    -        if (false === $this->isHrefSafeValue($href)) {
    -            $element->removeAttribute('href');
    -            $this->xmlIssues[] = array(
    -                'message' => 'Suspicious attribute \'href\'',
    -                'line' => $element->getLineNo(),
    -            );
    +        $this->cleanHrefAttributes($element);
    +    }
    +
    +    protected function cleanHrefAttributes(\DOMElement $element, string $prefix = ''): void
    +    {
    +        $relevantAttributes = array_filter(
    +            iterator_to_array($element->attributes),
    +            static function (\DOMAttr $attr) use ($prefix) {
    +                return strtolower($attr->name) === 'href' && strtolower($attr->prefix) === $prefix;
    +            }
    +        );
    +        foreach ($relevantAttributes as $attribute) {
    +            if (!$this->isHrefSafeValue($attribute->value)) {
    +                $element->removeAttribute($attribute->nodeName);
    +                $this->xmlIssues[] = array(
    +                    'message' => sprintf('Suspicious attribute \'%s\'', $attribute->nodeName),
    +                    'line' => $element->getLineNo(),
    +                );
    +                continue;
    +            }
    +            // in case the attribute name is `HrEf`/`xlink:HrEf`, adjust it to `href`/`xlink:href`
    +            if (!in_array($attribute->nodeName, $this->allowedAttrs, true)
    +                && in_array(strtolower($attribute->nodeName), $this->allowedAttrs, true)
    +            ) {
    +                $element->removeAttribute($attribute->nodeName);
    +                $element->setAttribute(strtolower($attribute->nodeName), $attribute->value);
    +            }
             }
         }
     
    
  • tests/data/hrefCleanOne.svg+11 4 modified
    @@ -1,8 +1,15 @@
     <svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" height="600px" id="Layer_1" width="600px" x="0px" y="0px" xml:space="preserve">
    -    <a>test 1</a>
    -    <a>test 2</a>
    -    <a href="#test3">test 3</a>
    -    <a xlink:href="#test">test 4</a>
    +    <a>test 1.a</a>
    +    <a>test 2.a</a>
    +    <a href="#test3">test 3.a</a>
    +    <a xlink:href="#test">test 4.a</a>
    +
    +    <a>test 1.b</a>
    +    <a>test 2.b</a>
    +    <a>test 2.c</a>
    +    <a href="#test3">test 3.b</a>
    +    <a xlink:href="#test">test 4.b</a>
    +    <a>test 4.c</a>
     
         <a>test 5</a>
         <a>test 6</a>
    
  • tests/data/hrefTestOne.svg+11 4 modified
    @@ -1,8 +1,15 @@
     <svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" height="600px" id="Layer_1" width="600px" x="0px" y="0px" xml:space="preserve">
    -    <a href="javascript:alert(2)">test 1</a>
    -    <a xlink:href="javascript:alert(2)">test 2</a>
    -    <a href="#test3">test 3</a>
    -    <a xlink:href="#test">test 4</a>
    +    <a href="javascript:alert(2)">test 1.a</a>
    +    <a xlink:href="javascript:alert(2)">test 2.a</a>
    +    <a href="#test3">test 3.a</a>
    +    <a xlink:href="#test">test 4.a</a>
    +
    +    <a HrEf="javascript:alert(2)">test 1.b</a>
    +    <a xlink:HrEf="javascript:alert(2)">test 2.b</a>
    +    <a XLinK:HrEf="javascript:alert(2)">test 2.c</a>
    +    <a HrEf="#test3">test 3.b</a>
    +    <a xlink:HrEf="#test">test 4.b</a>
    +    <a XLinK:HrEf="#test">test 4.c</a>
     
         <a href="data:data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' onload='alert(88)'%3E%3C/svg%3E">test 5</a>
         <a xlink:href="data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' onload='alert(88)'%3E%3C/svg%3E">test 6</a>
    
  • tests/data/svgCleanOne.svg+1 0 modified
    @@ -8,5 +8,6 @@
     <line fill="none" stroke="#000000" stroke-miterlimit="10" x1="457.114" y1="126.623" x2="458.753" y2="363.508"/>
     
     
    +
     <line fill="none" stroke="#000000" stroke-miterlimit="10" x1="541.54" y1="299.573" x2="543.179" y2="536.458"/>
     </svg>
    \ No newline at end of file
    
  • tests/data/svgTestOne.svg+1 0 modified
    @@ -11,5 +11,6 @@
     <line fill="none" stroke="#000000" stroke-miterlimit="10" x1="457.114" y1="126.623" x2="458.753" y2="363.508"/>
    
     <this>shouldn't be here</this>
    
     <script>alert(1);</script>
    
    +<ScRiPt>alert(1);</ScRiPt>
    
     <line fill="none" stroke="#000000" stroke-miterlimit="10" x1="541.54" y1="299.573" x2="543.179" y2="536.458"/>
    
     </svg>
    
    
5a0a1eaf0c6b

[SECURITY] Prevent bypass via mixed-case SVG attributes

https://github.com/darylldoyle/svg-sanitizerOliver HaderAug 11, 2025via ghsa
5 files changed · +62 23
  • src/Sanitizer.php+38 15 modified
    @@ -421,7 +421,7 @@ protected function cleanAttributesOnWhitelist(\DOMElement $element)
                  * Such as xlink:href when the xlink namespace isn't imported.
                  * We have to do this as the link is still ran in this case.
                  */
    -            if (false !== strpos($attrName, 'href')) {
    +            if (false !== stripos($attrName, 'href')) {
                     $href = $element->getAttribute($attrName);
                     if (false === $this->isHrefSafeValue($href)) {
                         $element->removeAttribute($attrName);
    @@ -453,14 +453,17 @@ protected function cleanAttributesOnWhitelist(\DOMElement $element)
          */
         protected function cleanXlinkHrefs(\DOMElement $element)
         {
    -        $xlinks = $element->getAttributeNS('http://www.w3.org/1999/xlink', 'href');
    -        if (false === $this->isHrefSafeValue($xlinks)) {
    -            $element->removeAttributeNS( 'http://www.w3.org/1999/xlink', 'href' );
    -            $this->xmlIssues[] = array(
    -                'message' => 'Suspicious attribute \'href\'',
    -                'line' => $element->getLineNo(),
    -            );
    +        foreach ($element->attributes as $attribute) {
    +            // remove attributes with unexpected namespace prefix, e.g. `XLinK:href` (instead of `xlink:href`)
    +            if ($attribute->prefix === '' && strtolower($attribute->nodeName) === 'xlink:href') {
    +                $element->removeAttribute($attribute->nodeName);
    +                $this->xmlIssues[] = array(
    +                    'message' => sprintf('Unexpected attribute \'%s\'', $attribute->nodeName),
    +                    'line' => $element->getLineNo(),
    +                );
    +            }
             }
    +        $this->cleanHrefAttributes($element, 'xlink');
         }
     
         /**
    @@ -470,13 +473,33 @@ protected function cleanXlinkHrefs(\DOMElement $element)
          */
         protected function cleanHrefs(\DOMElement $element)
         {
    -        $href = $element->getAttribute('href');
    -        if (false === $this->isHrefSafeValue($href)) {
    -            $element->removeAttribute('href');
    -            $this->xmlIssues[] = array(
    -                'message' => 'Suspicious attribute \'href\'',
    -                'line' => $element->getLineNo(),
    -            );
    +        $this->cleanHrefAttributes($element);
    +    }
    +
    +    protected function cleanHrefAttributes(\DOMElement $element, string $prefix = ''): void
    +    {
    +        $relevantAttributes = array_filter(
    +            iterator_to_array($element->attributes),
    +            static function (\DOMAttr $attr) use ($prefix) {
    +                return strtolower($attr->name) === 'href' && strtolower($attr->prefix) === $prefix;
    +            }
    +        );
    +        foreach ($relevantAttributes as $attribute) {
    +            if (!$this->isHrefSafeValue($attribute->value)) {
    +                $element->removeAttribute($attribute->nodeName);
    +                $this->xmlIssues[] = array(
    +                    'message' => sprintf('Suspicious attribute \'%s\'', $attribute->nodeName),
    +                    'line' => $element->getLineNo(),
    +                );
    +                continue;
    +            }
    +            // in case the attribute name is `HrEf`/`xlink:HrEf`, adjust it to `href`/`xlink:href`
    +            if (!in_array($attribute->nodeName, $this->allowedAttrs, true)
    +                && in_array(strtolower($attribute->nodeName), $this->allowedAttrs, true)
    +            ) {
    +                $element->removeAttribute($attribute->nodeName);
    +                $element->setAttribute(strtolower($attribute->nodeName), $attribute->value);
    +            }
             }
         }
     
    
  • tests/data/hrefCleanOne.svg+11 4 modified
    @@ -1,8 +1,15 @@
     <svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" height="600px" id="Layer_1" width="600px" x="0px" y="0px" xml:space="preserve">
    -    <a>test 1</a>
    -    <a>test 2</a>
    -    <a href="#test3">test 3</a>
    -    <a xlink:href="#test">test 4</a>
    +    <a>test 1.a</a>
    +    <a>test 2.a</a>
    +    <a href="#test3">test 3.a</a>
    +    <a xlink:href="#test">test 4.a</a>
    +
    +    <a>test 1.b</a>
    +    <a>test 2.b</a>
    +    <a>test 2.c</a>
    +    <a href="#test3">test 3.b</a>
    +    <a xlink:href="#test">test 4.b</a>
    +    <a>test 4.c</a>
     
         <a>test 5</a>
         <a>test 6</a>
    
  • tests/data/hrefTestOne.svg+11 4 modified
    @@ -1,8 +1,15 @@
     <svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" height="600px" id="Layer_1" width="600px" x="0px" y="0px" xml:space="preserve">
    -    <a href="javascript:alert(2)">test 1</a>
    -    <a xlink:href="javascript:alert(2)">test 2</a>
    -    <a href="#test3">test 3</a>
    -    <a xlink:href="#test">test 4</a>
    +    <a href="javascript:alert(2)">test 1.a</a>
    +    <a xlink:href="javascript:alert(2)">test 2.a</a>
    +    <a href="#test3">test 3.a</a>
    +    <a xlink:href="#test">test 4.a</a>
    +
    +    <a HrEf="javascript:alert(2)">test 1.b</a>
    +    <a xlink:HrEf="javascript:alert(2)">test 2.b</a>
    +    <a XLinK:HrEf="javascript:alert(2)">test 2.c</a>
    +    <a HrEf="#test3">test 3.b</a>
    +    <a xlink:HrEf="#test">test 4.b</a>
    +    <a XLinK:HrEf="#test">test 4.c</a>
     
         <a href="data:data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' onload='alert(88)'%3E%3C/svg%3E">test 5</a>
         <a xlink:href="data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' onload='alert(88)'%3E%3C/svg%3E">test 6</a>
    
  • tests/data/svgCleanOne.svg+1 0 modified
    @@ -8,5 +8,6 @@
     <line fill="none" stroke="#000000" stroke-miterlimit="10" x1="457.114" y1="126.623" x2="458.753" y2="363.508"/>
     
     
    +
     <line fill="none" stroke="#000000" stroke-miterlimit="10" x1="541.54" y1="299.573" x2="543.179" y2="536.458"/>
     </svg>
    \ No newline at end of file
    
  • tests/data/svgTestOne.svg+1 0 modified
    @@ -11,5 +11,6 @@
     <line fill="none" stroke="#000000" stroke-miterlimit="10" x1="457.114" y1="126.623" x2="458.753" y2="363.508"/>
    
     <this>shouldn't be here</this>
    
     <script>alert(1);</script>
    
    +<ScRiPt>alert(1);</ScRiPt>
    
     <line fill="none" stroke="#000000" stroke-miterlimit="10" x1="541.54" y1="299.573" x2="543.179" y2="536.458"/>
    
     </svg>
    
    

Vulnerability mechanics

Generated by null/stub on May 9, 2026. Inputs: CWE entries + fix-commit diffs from this CVE's patches. Citations validated against bundle.

References

7

News mentions

0

No linked articles in our index yet.