high · 8.2CVE-2026-84370Sep 8, 2026

CVE-2026-84370: svgo removeScripts Plugin XSS via Namespace and Control-Character Bypass

Rohit Hatagale
AI Security Researcher, SecureLayer7

SVGO's removeScripts plugin could be tricked into leaving executable JavaScript links intact by using a namespace-prefixed SVG anchor or hiding a javascript: URL behind an embedded tab or newline…

Packagesvgo
Ecosystemnpm
Affected>= 1.0.0, < 2.8.4
Fixed in2.8.4
CVE-2026-84370: svgo removeScripts Plugin XSS via Namespace and Control-Character Bypass

The problem

The removeScripts plugin only checked unprefixed SVG <a> elements. An attacker could instead use <svg:a> (with the prefix bound to the SVG namespace) and the plugin would leave the executable href untouched.

Separately, the plugin matched the javascript: scheme against the raw attribute value. Browsers silently strip ASCII tab (U+0009), line-feed (U+000A), and carriage-return (U+000D) from a URL before parsing the scheme, so a value like java&#9;script:alert(1) bypassed the string check but executed normally in-browser.

Proof of concept

A working proof-of-concept for CVE-2026-84370 in svgo, with the exact payload below.

text
<svg xmlns="http://www.w3.org/2000/svg" xmlns:svg="http://www.w3.org/2000/svg">
  <!-- Bypass 1: namespace-prefixed anchor -->
  <svg:a href="javascript:alert(document.cookie)">
    <text>click me</text>
  </svg:a>

  <!-- Bypass 2: tab character in scheme (&#9; = U+0009) -->
  <a href="java&#9;script:alert(document.cookie)">
    <text>click me too</text>
  </a>
</svg>

Two root causes share CWE-184 (Incomplete List of Disallowed Inputs). First, the element-name check was a plain string comparison against a, so svg:a was never matched and its href was never inspected. Second, the scheme check compared the raw attribute string against javascript: without first normalizing out the control characters that browsers strip before URL parsing, so java script: sailed through.

The patch added namespace-aware element matching (accepting only anchors in the SVG or default namespace) and applied a strip of \t, \n, and \r from the href value before the scheme comparison, closing both paths.

The fix

Upgrade to svgo 2.8.4 (plugin: removeScriptElement), 3.3.5, or 4.1.0 (plugin: removeScripts). For hostile input, run a dedicated SVG sanitizer such as DOMPurify before passing the file to SVGO. Avoid serving user-controlled SVGs in an active same-origin context.

Reporter not attributed.

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

Related research