Skip to content

Commit 4be7e00

Browse files
Fix raw-text sanitization bypass vulnerability and add regression tests (#5432)
1 parent 34dca7a commit 4be7e00

2 files changed

Lines changed: 58 additions & 2 deletions

File tree

‎packages/sanitize-html/index.js‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -566,13 +566,13 @@ function sanitizeHtml(html, options, _recursing) {
566566

567567
if (options.disallowedTagsMode === 'completelyDiscard' && !tagAllowed(tag)) {
568568
text = '';
569-
} else if ((options.disallowedTagsMode === 'discard' || options.disallowedTagsMode === 'completelyDiscard') && ((tag === 'script') || (tag === 'style'))) {
569+
} else if (tag && tagAllowed(tag) && (options.disallowedTagsMode === 'discard' || options.disallowedTagsMode === 'completelyDiscard') && ((tag === 'script') || (tag === 'style'))) {
570570
// htmlparser2 gives us these as-is. Escaping them ruins the content. Allowing
571571
// script tags is, by definition, game over for XSS protection, so if that's
572572
// your concern, don't allow them. The same is essentially true for style tags
573573
// which have their own collection of XSS vectors.
574574
result += text;
575-
} else if ((options.disallowedTagsMode === 'discard' || options.disallowedTagsMode === 'completelyDiscard') && (tag === 'textarea' || tag === 'xmp')) {
575+
} else if (tag && tagAllowed(tag) && (options.disallowedTagsMode === 'discard' || options.disallowedTagsMode === 'completelyDiscard') && (tag === 'textarea' || tag === 'xmp')) {
576576
// htmlparser2 treats <textarea> and <xmp> as raw text elements and
577577
// does NOT decode entities inside them. The text is already properly
578578
// encoded, so pass it through without additional escaping to avoid

‎packages/sanitize-html/test/test.js‎

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1931,4 +1931,60 @@ describe('sanitizeHtml', function() {
19311931
), '!<xmp>&lt;/xmp&gt;&lt;svg/onload=prompt`xs`&gt;</xmp>!'
19321932
);
19331933
});
1934+
1935+
describe('CVE-2026-44990 regression and raw-text edge cases', function() {
1936+
it('should escape raw-text inner content when xmp tag is disallowed and discarded under custom nonTextTags', function() {
1937+
assert.strictEqual(
1938+
sanitizeHtml('<xmp><script>alert(1)</script></xmp>', {
1939+
nonTextTags: ['script', 'style']
1940+
}),
1941+
'&lt;script&gt;alert(1)&lt;/script&gt;'
1942+
);
1943+
});
1944+
1945+
it('should escape raw-text inner content when script tag is disallowed and discarded under empty nonTextTags', function() {
1946+
assert.strictEqual(
1947+
sanitizeHtml('<script><svg onload=alert(1)></script>', {
1948+
nonTextTags: []
1949+
}),
1950+
'&lt;svg onload=alert(1)&gt;'
1951+
);
1952+
});
1953+
1954+
it('should escape raw-text inner content when textarea tag is disallowed and discarded', function() {
1955+
assert.strictEqual(
1956+
sanitizeHtml('<textarea><script>alert(1)</script></textarea>', {
1957+
nonTextTags: []
1958+
}),
1959+
'&lt;script&gt;alert(1)&lt;/script&gt;'
1960+
);
1961+
});
1962+
1963+
it('should escape raw-text inner content when style tag is disallowed and discarded', function() {
1964+
assert.strictEqual(
1965+
sanitizeHtml('<style><a onload=alert(1)></style>', {
1966+
nonTextTags: []
1967+
}),
1968+
'&lt;a onload=alert(1)&gt;'
1969+
);
1970+
});
1971+
1972+
it('should handle malformed or unclosed raw-text tags correctly', function() {
1973+
assert.strictEqual(
1974+
sanitizeHtml('<xmp><script>alert(1)', {
1975+
nonTextTags: ['script', 'style']
1976+
}),
1977+
'&lt;script&gt;alert(1)'
1978+
);
1979+
});
1980+
1981+
it('should handle sibling raw-text elements correctly without leaking states', function() {
1982+
assert.strictEqual(
1983+
sanitizeHtml('<noembed><script>alert(1)</script></noembed><noscript><style>body{}</style></noscript>', {
1984+
nonTextTags: []
1985+
}),
1986+
'alert(1)body{}'
1987+
);
1988+
});
1989+
});
19341990
});

0 commit comments

Comments
 (0)