GitHub Advisoryadvisory · npm

GHSA-374f-7chj-9948 ApostropheCMS sanitize-html: SMIL animation namespace-prefix bypass allows javascript: URI injection (XSS)

LOW Published 2026-10-20 Modified 2026-10-20 Official page ↗
Aliases
CVE-2026-113657
Ecosystem
npm
Packages
sanitize-html
Severity
Low
CVSS vector
CVSS:3.1/AV:N/AC:H/PR:L/UI:R/S:U/C:L/I:L/A:N
Weaknesses
CWE-79
Reviewed
2026-10-20

Affected packages

• npm sanitize-html: >= 2.17.7, < 2.18.0 → fixed in 2.18.0

Description

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( `Click`, config ); console.log("Standard animate:", blocked.includes("javascript:") ? "BYPASS" : "BLOCKED"); // Output: BLOCKED // Namespace-prefixed animate — BYPASSES the fix const bypassed = sanitizeHtml( `Click`, 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 `` to override the `href` attribute of a parent `` 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

Related

Alias · CVE (1)

  • CVE-2026-113657 ApostropheCMS is an open-source Node.js content management system, and sanitize-html provides a simple HTML sanitizer with a clear API. vulnerability

Weakness · CWE (1)

  • CWE-79 Improper Neutralization of Input During Web Page Generation weakness

References