Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions packages/sanitize-html/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -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 <textarea> and <xmp> as raw text elements and
// does NOT decode entities inside them. The text is already properly
// encoded, so pass it through without additional escaping to avoid
Expand Down
56 changes: 56 additions & 0 deletions packages/sanitize-html/test/test.js
Original file line number Diff line number Diff line change
Expand Up @@ -1931,4 +1931,60 @@ describe('sanitizeHtml', function() {
), '!<xmp>&lt;/xmp&gt;&lt;svg/onload=prompt`xs`&gt;</xmp>!'
);
});

describe('CVE-2026-44990 regression and raw-text edge cases', function() {
it('should escape raw-text inner content when xmp tag is disallowed and discarded under custom nonTextTags', function() {
assert.strictEqual(
sanitizeHtml('<xmp><script>alert(1)</script></xmp>', {
nonTextTags: ['script', 'style']
}),
'&lt;script&gt;alert(1)&lt;/script&gt;'
);
});

it('should escape raw-text inner content when script tag is disallowed and discarded under empty nonTextTags', function() {
assert.strictEqual(
sanitizeHtml('<script><svg onload=alert(1)></script>', {
nonTextTags: []
}),
'&lt;svg onload=alert(1)&gt;'
);
});

it('should escape raw-text inner content when textarea tag is disallowed and discarded', function() {
assert.strictEqual(
sanitizeHtml('<textarea><script>alert(1)</script></textarea>', {
nonTextTags: []
}),
'&lt;script&gt;alert(1)&lt;/script&gt;'
);
});

it('should escape raw-text inner content when style tag is disallowed and discarded', function() {
assert.strictEqual(
sanitizeHtml('<style><a onload=alert(1)></style>', {
nonTextTags: []
}),
'&lt;a onload=alert(1)&gt;'
);
});

it('should handle malformed or unclosed raw-text tags correctly', function() {
assert.strictEqual(
sanitizeHtml('<xmp><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{}'
);
});
});
});