sanitize-html 2.17.5-alpha.1 → 2.17.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +8 -1
- package/index.js +51 -14
- package/package.json +6 -3
package/README.md
CHANGED
|
@@ -182,7 +182,14 @@ selfClosing: [ 'img', 'br', 'hr', 'area', 'base', 'basefont', 'input', 'link', '
|
|
|
182
182
|
// URL schemes we permit
|
|
183
183
|
allowedSchemes: [ 'http', 'https', 'ftp', 'mailto', 'tel' ],
|
|
184
184
|
allowedSchemesByTag: {},
|
|
185
|
-
allowedSchemesAppliedToAttributes: [
|
|
185
|
+
allowedSchemesAppliedToAttributes: [
|
|
186
|
+
'href', 'src', 'cite',
|
|
187
|
+
'action', 'formaction', 'data', 'xlink:href',
|
|
188
|
+
'poster', 'background', 'ping',
|
|
189
|
+
'longdesc', 'usemap', 'codebase', 'classid', 'archive',
|
|
190
|
+
'profile', 'manifest', 'itemid',
|
|
191
|
+
'dynsrc', 'lowsrc'
|
|
192
|
+
],
|
|
186
193
|
allowProtocolRelative: true,
|
|
187
194
|
enforceHtmlBoundary: false,
|
|
188
195
|
parseStyleAttributes: true
|
package/index.js
CHANGED
|
@@ -440,11 +440,11 @@ function sanitizeHtml(html, options, _recursing) {
|
|
|
440
440
|
return;
|
|
441
441
|
}
|
|
442
442
|
}
|
|
443
|
-
if (a === 'srcset') {
|
|
443
|
+
if (a === 'srcset' || a === 'imagesrcset') {
|
|
444
444
|
try {
|
|
445
445
|
let parsed = parseSrcset(value);
|
|
446
446
|
parsed.forEach(function(value) {
|
|
447
|
-
if (naughtyHref(
|
|
447
|
+
if (naughtyHref(a, value.url)) {
|
|
448
448
|
value.evil = true;
|
|
449
449
|
}
|
|
450
450
|
});
|
|
@@ -540,8 +540,13 @@ function sanitizeHtml(html, options, _recursing) {
|
|
|
540
540
|
result += ' />';
|
|
541
541
|
} else {
|
|
542
542
|
result += '>';
|
|
543
|
-
if (frame.innerText && !hasText
|
|
544
|
-
|
|
543
|
+
if (frame.innerText && !hasText) {
|
|
544
|
+
const escaped = escapeHtml(frame.innerText);
|
|
545
|
+
if (options.textFilter) {
|
|
546
|
+
result += options.textFilter(escaped, name);
|
|
547
|
+
} else {
|
|
548
|
+
result += escaped;
|
|
549
|
+
}
|
|
545
550
|
addedText = true;
|
|
546
551
|
}
|
|
547
552
|
}
|
|
@@ -573,14 +578,39 @@ function sanitizeHtml(html, options, _recursing) {
|
|
|
573
578
|
// which have their own collection of XSS vectors.
|
|
574
579
|
result += text;
|
|
575
580
|
} else if (tag && tagAllowed(tag) && (options.disallowedTagsMode === 'discard' || options.disallowedTagsMode === 'completelyDiscard') && (tag === 'textarea' || tag === 'xmp')) {
|
|
576
|
-
//
|
|
577
|
-
//
|
|
578
|
-
//
|
|
579
|
-
//
|
|
580
|
-
//
|
|
581
|
-
//
|
|
582
|
-
//
|
|
583
|
-
|
|
581
|
+
// <textarea> and <xmp> hold text that must not be re-emitted verbatim:
|
|
582
|
+
// if a raw `<` survives into the output it can reopen a tag when the
|
|
583
|
+
// result is re-parsed by a browser, smuggling non-allowlisted markup
|
|
584
|
+
// through the allowlist (mutation-XSS, GHSA-jxwj-j7wr-gfrw — e.g. the
|
|
585
|
+
// `</textarea/>` solidus mis-close). We therefore ALWAYS escape this
|
|
586
|
+
// content rather than passing it through. Escaping unconditionally also
|
|
587
|
+
// closes the related SVG/MathML foreign-content bypass (where the HTML5
|
|
588
|
+
// parser treats <textarea>/<xmp> as ordinary foreign elements and a
|
|
589
|
+
// browser re-parses their contents as live markup, e.g.
|
|
590
|
+
// `<svg><textarea><img src=x onerror=alert(1)></textarea></svg>`): since
|
|
591
|
+
// we never re-emit raw text for these tags, no namespace check is
|
|
592
|
+
// needed. The two tags need different escaping because htmlparser2
|
|
593
|
+
// tokenizes them differently:
|
|
594
|
+
if (tag === 'xmp') {
|
|
595
|
+
// <xmp> is a raw-text (CDATA) element: entities are NOT decoded, so
|
|
596
|
+
// its content reaches us as raw source that is already entity-encoded.
|
|
597
|
+
// Escape only the angle brackets so a literal `<` cannot reopen a tag,
|
|
598
|
+
// while leaving `&` untouched to avoid double-encoding entities that
|
|
599
|
+
// are already encoded in the source.
|
|
600
|
+
result += text.replace(/</g, '<').replace(/>/g, '>');
|
|
601
|
+
} else {
|
|
602
|
+
// <textarea> is an RCDATA element: htmlparser2 (>= 11) decodes
|
|
603
|
+
// entities inside it, so its content reaches us as plain decoded text.
|
|
604
|
+
// It must therefore be fully escaped like any other text — escaping
|
|
605
|
+
// `&` as well as `<`/`>` — so entities round-trip faithfully (no
|
|
606
|
+
// double-encoding, see the CVE-2026-40186 regression tests) and no
|
|
607
|
+
// `<` can reopen a tag.
|
|
608
|
+
result += escapeHtml(text, false);
|
|
609
|
+
}
|
|
610
|
+
// Other "nonTextTags" like <option> are not raw text elements in
|
|
611
|
+
// htmlparser2, so their contents are decoded and must be escaped below
|
|
612
|
+
// like any other text (important to prevent XSS via entity-encoded
|
|
613
|
+
// payloads such as <option><script>...</script></option>).
|
|
584
614
|
} else if (!addedText) {
|
|
585
615
|
const escaped = escapeHtml(text, false);
|
|
586
616
|
if (options.textFilter) {
|
|
@@ -953,11 +983,18 @@ sanitizeHtml.defaults = {
|
|
|
953
983
|
'alt'
|
|
954
984
|
],
|
|
955
985
|
// Lots of these won't come up by default because we don't allow them
|
|
956
|
-
selfClosing: [ 'img', 'br', 'hr', 'area', 'base', 'basefont', 'input', 'link', 'meta' ],
|
|
986
|
+
selfClosing: [ 'img', 'br', 'hr', 'area', 'base', 'basefont', 'input', 'link', 'meta', 'col' ],
|
|
957
987
|
// URL schemes we permit
|
|
958
988
|
allowedSchemes: [ 'http', 'https', 'ftp', 'mailto', 'tel' ],
|
|
959
989
|
allowedSchemesByTag: {},
|
|
960
|
-
allowedSchemesAppliedToAttributes: [
|
|
990
|
+
allowedSchemesAppliedToAttributes: [
|
|
991
|
+
'href', 'src', 'cite',
|
|
992
|
+
'action', 'formaction', 'data', 'xlink:href',
|
|
993
|
+
'poster', 'background', 'ping',
|
|
994
|
+
'longdesc', 'usemap', 'codebase', 'classid', 'archive',
|
|
995
|
+
'profile', 'manifest', 'itemid',
|
|
996
|
+
'dynsrc', 'lowsrc'
|
|
997
|
+
],
|
|
961
998
|
allowProtocolRelative: true,
|
|
962
999
|
enforceHtmlBoundary: false,
|
|
963
1000
|
parseStyleAttributes: true,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "sanitize-html",
|
|
3
|
-
"version": "2.17.
|
|
3
|
+
"version": "2.17.6",
|
|
4
4
|
"description": "Clean up user-submitted HTML, preserving allowlisted elements and allowlisted attributes on a per-element basis",
|
|
5
5
|
"sideEffects": false,
|
|
6
6
|
"main": "index.js",
|
|
@@ -21,14 +21,17 @@
|
|
|
21
21
|
],
|
|
22
22
|
"author": "Apostrophe Technologies, Inc.",
|
|
23
23
|
"license": "MIT",
|
|
24
|
+
"engines": {
|
|
25
|
+
"node": ">=22.12.0"
|
|
26
|
+
},
|
|
24
27
|
"dependencies": {
|
|
25
28
|
"deepmerge": "^4.2.2",
|
|
26
29
|
"escape-string-regexp": "^4.0.0",
|
|
27
|
-
"htmlparser2": "^
|
|
30
|
+
"htmlparser2": "^12.0.0",
|
|
28
31
|
"is-plain-object": "^5.0.0",
|
|
29
32
|
"parse-srcset": "^1.0.2",
|
|
30
33
|
"postcss": "^8.3.11",
|
|
31
|
-
"launder": "^1.7.
|
|
34
|
+
"launder": "^1.7.1"
|
|
32
35
|
},
|
|
33
36
|
"devDependencies": {
|
|
34
37
|
"eslint": "^9.39.1",
|