From 2c7ca256bbfdca1a8ae02c6b2b1009ba62f1167b Mon Sep 17 00:00:00 2001 From: Dipanshu Singh Date: Sat, 23 May 2026 09:39:42 +0530 Subject: [PATCH] Fix raw-text sanitization bypass vulnerability and add regression tests --- packages/sanitize-html/index.js | 4 +-- packages/sanitize-html/test/test.js | 56 +++++++++++++++++++++++++++++ 2 files changed, 58 insertions(+), 2 deletions(-) diff --git a/packages/sanitize-html/index.js b/packages/sanitize-html/index.js index 6a67683868..b5da859f7e 100644 --- a/packages/sanitize-html/index.js +++ b/packages/sanitize-html/index.js @@ -566,13 +566,13 @@ function sanitizeHtml(html, options, _recursing) { if (options.disallowedTagsMode === 'completelyDiscard' && !tagAllowed(tag)) { text = ''; - } else if ((options.disallowedTagsMode === 'discard' || options.disallowedTagsMode === 'completelyDiscard') && ((tag === 'script') || (tag === 'style'))) { + } else if (tag && tagAllowed(tag) && (options.disallowedTagsMode === 'discard' || options.disallowedTagsMode === 'completelyDiscard') && ((tag === 'script') || (tag === 'style'))) { // htmlparser2 gives us these as-is. Escaping them ruins the content. Allowing // script tags is, by definition, game over for XSS protection, so if that's // your concern, don't allow them. The same is essentially true for style tags // which have their own collection of XSS vectors. result += text; - } else if ((options.disallowedTagsMode === 'discard' || options.disallowedTagsMode === 'completelyDiscard') && (tag === 'textarea' || tag === 'xmp')) { + } else if (tag && tagAllowed(tag) && (options.disallowedTagsMode === 'discard' || options.disallowedTagsMode === 'completelyDiscard') && (tag === 'textarea' || tag === 'xmp')) { // htmlparser2 treats ', { + nonTextTags: [] + }), + '<script>alert(1)</script>' + ); + }); + + it('should escape raw-text inner content when style tag is disallowed and discarded', function() { + assert.strictEqual( + sanitizeHtml('', { + nonTextTags: [] + }), + '<a onload=alert(1)>' + ); + }); + + it('should handle malformed or unclosed raw-text tags correctly', function() { + assert.strictEqual( + sanitizeHtml('<script>alert(1)', { + nonTextTags: ['script', 'style'] + }), + '&lt;script&gt;alert(1)' + ); + }); + + it('should handle sibling raw-text elements correctly without leaking states', function() { + assert.strictEqual( + sanitizeHtml('<noembed><script>alert(1)</script></noembed><noscript><style>body{}</style></noscript>', { + nonTextTags: [] + }), + 'alert(1)body{}' + ); + }); + }); });