SVG Security Best Practices: Preventing XSS and Injection Attacks
Most developers treat SVGs like images (PNGs or JPGs). But SVG is XML code. It's a document format that supports scripting, external resources, and event handlers.
This makes SVG a powerful attack vector. A malicious user can upload an SVG that executes JavaScript in your users' browsers, steals cookies, or performs CSRF attacks.
If you allow users to upload SVGs, or if you render SVGs from untrusted sources, this guide is mandatory reading.
The Threat: Stored XSS
The most common attack is Stored Cross-Site Scripting (XSS). An attacker embeds a script in an SVG:
<svg xmlns="http://www.w3.org/2000/svg">
<script>alert('XSS')</script>
</svg>
Or uses event handlers:
<svg xmlns="http://www.w3.org/2000/svg" onload="alert('XSS')">
<rect x="0" y="0" width="100" height="100" />
</svg>
When another user views this "image," the script executes in their session.
How to Defend Your App
1. Serve as an Image (<img>)
The safest way to render untrusted SVG is using the <img> tag.
<!-- Safe -->
<img src="user-upload.svg" alt="User content" />
Browsers automatically disable scripts and external resource loading for SVGs loaded inside <img> tags. The script is technically still there, but it won't run.
However, this blocks animations and interactivity.
2. Sanitization (The Gold Standard)
If you must inline the SVG (e.g., <svg>...</svg> in your HTML) or allow users to download it, you must sanitize it.
Sanitization involves parsing the XML and stripping out dangerous tags (<script>, <foreignObject>) and attributes (onload, onclick, javascript: links).
Recommended Tool: DOMPurify
For frontend sanitization:
import DOMPurify from 'dompurify';
const dirtySVG = '<svg onload="alert(1)">...</svg>';
const cleanSVG = DOMPurify.sanitize(dirtySVG, {
USE_PROFILES: { svg: true, svgFilters: true }
});
// Now safe to inject
document.getElementById('container').innerHTML = cleanSVG;
Backend Sanitization:
Never trust client-side sanitization alone. Use a library like bleach (Python) or sanitize-html (Node.js) configured specifically for SVG allowlists.
3. Content Security Policy (CSP)
CSP is your safety net. If a script slips through, CSP prevents it from executing.
Content-Security-Policy: default-src 'self'; img-src 'self' data:; script-src 'self';
If your CSP forbids inline scripts (script-src 'self'), an SVG with <script>alert(1)</script> will be blocked by the browser even if you display it directly.
4. Content-Disposition Header
If you host user SVGs for download or direct viewing (e.g., yoursite.com/uploads/file.svg), ensure the server sends the correct headers so the browser doesn't execute it.
Content-Type: image/svg+xml
Content-Security-Policy: script-src 'none'; object-src 'none';
Content-Disposition: attachment; filename="download.svg"
Forcing Content-Disposition: attachment ensures the file is downloaded rather than rendered in the browser window, neutralizing XSS risks.
Dangerous Features to Watch For
When configuring a custom sanitizer, block these elements and attributes:
Elements:
<script><foreignObject>(can contain HTML/JS)<iframe(if valid in context)<use>(can reference external malicious resources)
Attributes:
onload,onclick,onmouseover, etc.xlink:hrefpointing tojavascript:URLshrefpointing tojavascript:URLs
The "Billions Laughs" Attack (XML Bomb)
SVG is XML, so it's vulnerable to XML Entity Expansion attacks.
<!DOCTYPE svg [
<!ENTITY lol "lol">
<!ENTITY lol1 "&lol;&lol;&lol;&lol;&lol;&lol;&lol;&lol;&lol;&lol;">
...
]>
<svg>&lol9;</svg>
This recurses exponentially, consuming server RAM and causing Denial of Service (DoS).
Fix: Disable DTD processing (DOCTYPE) in your XML parser on the backend. Most modern XML parsers disable this by default, but verify your configuration.
Summary Checklist
- Prefer
<img>tags for untrusted content. - Sanitize on the server before saving/serving.
- Sanitize on the client (DOMPurify) before inlining.
- Use strict CSP headers.
- Disable XML DTDs to prevent DoS.
SVG is fantastic, but it's a document, not just a picture. Treat it with the same caution you'd give to an HTML file.
Related Articles: