high · 8.2Jul 21, 2026

svgo removeScripts Plugin XSS Bypass via Namespace Prefix and Case-Insensitive URI

Shubham Kandhare
Security Engagement Manager, SecureLayer7

SVGO's script-removal plugin could be bypassed by using a namespace-prefixed script tag like <svg:script> or a mixed-case JavaScript URI like JavaScript:, leaving executable code in the optimized SVG.

Packagesvgo
Ecosystemnpm
Affected>= 1.0.0, < 2.8.3
Fixed in2.8.3

The problem

The removeScripts plugin (called removeScriptElement in v1-v3) is disabled by default but exists for users who rely on SVGO to sanitize SVG before serving it.

Two gaps let scripts survive sanitization: the plugin matched only bare `script` element names, so `<svg:script>` was invisible to it. Separately, JavaScript URI matching was case-sensitive, so `JavaScript:alert(1)` in an `href` attribute was not stripped.

Proof of concept

A working proof-of-concept for this issue in svgo, with the exact payload below.

javascript
<svg xmlns="http://www.w3.org/2000/svg"
     xmlns:svg="http://www.w3.org/2000/svg"
     xmlns:uwu="http://www.w3.org/1999/xlink"
     viewBox="0 0 100 100" version="1.1">
  <!-- Bypass 1: namespaced script tag, not matched by bare-name check -->
  <svg:script>
    alert(document.cookie);
  </svg:script>
  <!-- Bypass 2: mixed-case URI scheme, not matched by case-sensitive check -->
  <a uwu:href="JavaScript:(() => { alert(document.cookie) })();">
    <text y="30">click me</text>
  </a>
</svg>

The root cause is an incomplete blocklist (CWE-184) combined with improper input neutralization (CWE-79). The old plugin compared element names as plain strings, so `svg:script` never equaled `script`. The URI check used a case-sensitive regex or string match, so `JavaScript:` passed through untouched.

The patch rewired element matching to be namespace-aware, explicitly accepting only elements in the SVG or XHTML namespace, and switched the JavaScript URI check to a case-insensitive comparison. This means a custom `<foo:script>` in an unrelated namespace is now left alone correctly, while all browser-executable variants are caught.

The fix

Upgrade to svgo 2.8.3 (v2), 3.3.4 (v3), or 4.0.2 (v4). If you cannot upgrade, replace the removeScripts/removeScriptElement plugin with a dedicated SVG sanitizer such as DOMPurify before passing the SVG to SVGO.

Reported by SethFalco.

References: [1][2][3][4][5][6]

Related research