CVE-2026-113657
ApostropheCMS sanitize-html: SMIL animation namespace-prefix bypass allows javascript: URI injection (XSS)
ApostropheCMS is an open-source Node.js content management system, and sanitize-html provides a simple HTML sanitizer with a clear API. From sanitize-html 2.17.7 until 2.18.0, an application configuration that permits namespace-prefixed SVG animation elements and the relevant URL-targeting attributes can bypass the SMIL URL protection because prefixed tag names do not match the unprefixed animation-element check. The bypass can rewrite a link destination to an unsafe URI after the sanitized SVG is rendered, with script execution occurring when a user activates the affected link; the default sanitizer configuration is not unconditionally affected. This issue is fixed in sanitize-html 2.18.0.
Attack profile
How easy it is to exploit
- Attack vector
- Network
- Complexity
- High
- Privileges needed
- Low
- User interaction
- Required
What an attacker can affect
- Confidentiality
- Low
- Integrity
- Low
- Availability
- None
Exploit likelihood
What each source reports
CVE List Score used
3.7
ApostropheCMS sanitize-html: SMIL animation namespace-prefix bypass allows javascript: URI injection (XSS)
ApostropheCMS is an open-source Node.js content management system, and sanitize-html provides a simple HTML sanitizer with a clear API. From sanitize-html 2.17.7 until 2.18.0, an application configuration that permits namespace-prefixed SVG animation elements and the relevant URL-targeting attributes can bypass the SMIL URL protection because prefixed tag names do not match the unprefixed animation-element check. The bypass can rewrite a link destination to an unsafe URI after the sanitized SVG is rendered, with script execution occurring when a user activates the affected link; the default sanitizer configuration is not unconditionally affected. This issue is fixed in sanitize-html 2.18.0.
- Record
CVE-2026-113657- CVSS 3.1
- 3.7 Low
CVSS:3.1/AV:N/AC:H/PR:L/UI:R/S:U/C:L/I:L/A:N - Weakness
- CWE-79
- Updated
- 2026-11-02
EUVDEUVD-2026-105186
3.7
ApostropheCMS is an open-source Node.js content management system, and sanitize-html provides a simple HTML sanitizer with a clear API. From sanitize-html 2.17.7 until 2.18.0, an application configuration that permits namespace-prefixed SVG animation elements and the relevant URL-targeting attributes can bypass the SMIL URL protection because prefixed tag names do not match the unprefixed animation-element check. The bypass can rewrite a link destination to an unsafe URI after the sanitized SVG is rendered, with script execution occurring when a user activates the affected link; the default sanitizer configuration is not unconditionally affected. This issue is fixed in sanitize-html 2.18.0.
- Record
EUVD-2026-105186- CVSS 3.1
- 3.7 Low
CVSS:3.1/AV:N/AC:H/PR:L/UI:R/S:U/C:L/I:L/A:N - Updated
- 2026-11-02
GitHub AdvisoriesGHSA-374f-7chj-9948
3.7
ApostropheCMS sanitize-html: SMIL animation namespace-prefix bypass allows javascript: URI injection (XSS)
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
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:
// 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
- Record
GHSA-374f-7chj-9948- CVSS 3.1
- 3.7 Low
CVSS:3.1/AV:N/AC:H/PR:L/UI:R/S:U/C:L/I:L/A:N - Weakness
- CWE-79
- Updated
- 2026-11-02
NVD
3.7
ApostropheCMS is an open-source Node.js content management system, and sanitize-html provides a simple HTML sanitizer with a clear API. From sanitize-html 2.17.7 until 2.18.0, an application configuration that permits namespace-prefixed SVG animation elements and the relevant URL-targeting attributes can bypass the SMIL URL protection because prefixed tag names do not match the unprefixed animation-element check. The bypass can rewrite a link destination to an unsafe URI after the sanitized SVG is rendered, with script execution occurring when a user activates the affected link; the default sanitizer configuration is not unconditionally affected. This issue is fixed in sanitize-html 2.18.0.
- Record
CVE-2026-113657- CVSS 3.1
- 3.7 Low
CVSS:3.1/AV:N/AC:H/PR:L/UI:R/S:U/C:L/I:L/A:N - Weakness
- CWE-79
- Updated
- 2026-11-02
OSV
3.7
ApostropheCMS sanitize-html: SMIL animation namespace-prefix bypass allows javascript: URI injection (XSS)
ApostropheCMS is an open-source Node.js content management system, and sanitize-html provides a simple HTML sanitizer with a clear API. From sanitize-html 2.17.7 until 2.18.0, an application configuration that permits namespace-prefixed SVG animation elements and the relevant URL-targeting attributes can bypass the SMIL URL protection because prefixed tag names do not match the unprefixed animation-element check. The bypass can rewrite a link destination to an unsafe URI after the sanitized SVG is rendered, with script execution occurring when a user activates the affected link; the default sanitizer configuration is not unconditionally affected. This issue is fixed in sanitize-html 2.18.0.
- Record
CVE-2026-113657- CVSS 3.1
- 3.7 Low
CVSS:3.1/AV:N/AC:H/PR:L/UI:R/S:U/C:L/I:L/A:N - Weakness
- CWE-79
- Updated
- 2026-11-02
OSVGHSA-374f-7chj-9948
3.7
ApostropheCMS sanitize-html: SMIL animation namespace-prefix bypass allows javascript: URI injection (XSS)
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
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:
// 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
- Record
GHSA-374f-7chj-9948- CVSS 3.1
- 3.7 Low
CVSS:3.1/AV:N/AC:H/PR:L/UI:R/S:U/C:L/I:L/A:N - Weakness
- CWE-79
- Updated
- 2026-11-02
Affected products
Affected packages
| Package | Affected | Fixed |
|---|---|---|
sanitize-htmlnpm |
2.17.7 up to 2.18.0 | 2.18.0 |
History
| Recorded | Changed | Sources |
|---|---|---|
| first indexed | CVE List, EUVD, GitHub Advisories, NVD, OSV |
Dates are when Every CVE recorded each change. Upstream dates may differ.
In short
CVE-2026-113657 is a low vulnerability (CVSS 3.7) in Apostrophecms Apostrophe, published October 20, 2026. No source lists public exploit code. EPSS puts the chance of exploitation in the next 30 days at 0.31%, the 24th percentile. Fixed in sanitize-html 2.18.0.
References
github.com/apostrophecms/apostrophe/security/advisories/GHSA-374f-7chj-9948
github.com/apostrophecms/apostrophe/commit/9500b8b0e1d630d5e07fbb5fd2c25ed0943845ce
github.com/apostrophecms/apostrophe/commit/f84fd41cbf6c22eb8d0008fc9c25df1f7130b350
github.com/apostrophecms/apostrophe/releases/tag/[email protected]
github.com/advisories/GHSA-374f-7chj-9948
github.com/CVEProject/cvelistV5/tree/main/cves/2026/113xxx/CVE-2026-113657.json
nvd.nist.gov/vuln/detail/CVE-2026-113657
github.com/apostrophecms/apostrophe