GHSA-374f-7chj-9948: XSS

Published Oct 20, 2026
·
Updated

Summary

sanitize-html 2.17.7 introduced animatesUrlAttribute() to block SVG animation elements (animate, animateColor, animateMotion, animateTransform, set) when their attributeName targets a URL sink like href. However, this check is bypassed when the animation element uses a namespace prefix (e.g., svg:animate instead of animate).

Root Cause

The animatesUrlAttribute function (index.js, line ~832) checks svgAnimationTags.indexOf(name.toLowerCase()), where svgAnimationTags contains bare local names: ["animate", "animatecolor", "animatemotion", "animatetransform", "set"]. htmlparser2 passes namespace-prefixed tag names verbatim, so svg:animate does not match animate in the array, causing the function to return false and skip the SMIL XSS protection.

Ironically, the function already handles namespace prefixes correctly for the attributeName target value (line ~843): const localName = target.slice(target.lastIndexOf(":") + 1) — but this same stripping was not applied to the tag name itself.

Proof of Concept

javascript const sanitizeHtml = require("sanitize-html");

const config = { allowedTags: false, allowedAttributes: false, allowedSchemes: ["http", "https"] };

// Standard animate — BLOCKED by 2.17.7 fix const blocked = sanitizeHtml( <svg><a href="#safe"><animate attributeName="href" values="#safe;javascript:alert(1)" dur=".01s" fill="freeze"/><text>Click</text></a></svg>, config ); console.log("Standard animate:", blocked.includes("javascript:") ? "BYPASS" : "BLOCKED"); // Output: BLOCKED

// Namespace-prefixed animate — BYPASSES the fix const bypassed = sanitizeHtml( <svg><a href="#safe"><svg:animate attributeName="href" values="#safe;javascript:alert(document.domain)" dur=".01s" fill="freeze"/><text>Click</text></a></svg>, config ); console.log("svg:animate:", bypassed.includes("javascript:") ? "BYPASS" : "BLOCKED"); // Output: BYPASS

All 5 SMIL animation elements are bypassable with any namespace prefix (svg:, xlink:, xml:, uppercase variants). Tested and confirmed on sanitize-html 2.17.7 from the apostrophe monorepo.

Impact

Cross-site scripting (XSS) via SMIL animation retargeting. When an application uses allowedTags: false (common in rich-text editors that want to preserve SVG), an attacker can inject <svg:animate attributeName="href" values="#safe;javascript:..." .../> to override the href attribute of a parent <a> element after the page loads, causing the link to execute JavaScript when clicked.

Suggested Fix

Strip the namespace prefix from the tag name before the lookup, matching the pattern already used for the attribute name target:

javascript // Before (vulnerable): if (svgAnimationTags.indexOf(name.toLowerCase()) === -1) {

// After (fixed): const localTagName = name.toLowerCase().slice(name.lastIndexOf(":") + 1); if (svgAnimationTags.indexOf(localTagName) === -1) {

Affected Versions

sanitize-html >= 2.17.7 (where the SMIL animation protection was introduced). Earlier versions lack the SMIL protection entirely.

Credit

Kai Aizen (SnailSploit) — Adversarial AI & Security Research

Affected Software

1 affected componentFixes available
npm/sanitize-html>=2.17.7<2.18.0
2.18.0

Remediation

Recommended actions to resolve this vulnerability, in priority order.

  1. Upgrade

    Upgrade npm/sanitize-html to a version that resolves this vulnerability.

    Fixed in 2.18.0
  2. Compensating control

    In sanitize-html's animatesUrlAttribute function, strip the namespace prefix from the tag name before checking it against svgAnimationTags: use name.toLowerCase().slice(name.lastIndexOf(":") + 1) and compare the resulting localTagName, so namespace-prefixed elements such as svg:animate are protected.

Event History

Oct 20, 2026
Advisory Published
via GitHub·06:21 PM
Data Sourced
via GitHub·06:21 PM
DescriptionSeverityWeaknessAffected Software

Contact

SecAlerts Pty Ltd.
132 Wickham Terrace
Fortitude Valley,
QLD 4006, Australia
info@secalerts.co
By using SecAlerts services, you agree to our services end-user license agreement. This website is safeguarded by reCAPTCHA and governed by the Google Privacy Policy and Terms of Service. All names, logos, and brands of products are owned by their respective owners, and any usage of these names, logos, and brands for identification purposes only does not imply endorsement. If you possess any content that requires removal, please get in touch with us.
© 2026 SecAlerts Pty Ltd.
ABN: 70 645 966 203, ACN: 645 966 203