test: add XSS-prevention suite for utils/sanitize

8 tests locking in security-critical behavior of sanitizeCustomHtml /
sanitizeText: script-content removal, event-handler stripping, javascript:
link neutralization, anchor hardening (noreferrer/noopener/_blank), non-mxc
<img> → link conversion, and the N100 <pre class> language-* restriction.
Verified against actual sanitize-html behavior. Suite now 27 tests.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
2026-06-30 09:27:22 -04:00
parent 2a0478cad8
commit 472d4ba008
+52
View File
@@ -0,0 +1,52 @@
import { test } from 'node:test';
import assert from 'node:assert/strict';
import { sanitizeCustomHtml, sanitizeText } from './sanitize';
test('sanitizeText escapes HTML metacharacters', () => {
assert.equal(sanitizeText('<script>'), '&lt;script&gt;');
assert.equal(sanitizeText('a & b'), 'a &amp; b');
assert.equal(sanitizeText(`"'`), '&quot;&#39;');
assert.equal(sanitizeText('plain text'), 'plain text');
});
test('sanitizeCustomHtml removes <script> content', () => {
const out = sanitizeCustomHtml('<script>alert(1)</script>hello');
assert.ok(!out.includes('alert'));
assert.ok(out.includes('hello'));
});
test('sanitizeCustomHtml strips event-handler attributes', () => {
const out = sanitizeCustomHtml('<b onclick="evil()">hi</b>');
assert.ok(!out.includes('onclick'));
assert.ok(out.includes('hi'));
});
test('sanitizeCustomHtml drops disallowed tags, keeps allowed ones', () => {
assert.ok(!sanitizeCustomHtml('<iframe src="x"></iframe>').includes('iframe'));
assert.ok(sanitizeCustomHtml('<strong>bold</strong>').includes('<strong>'));
});
test('sanitizeCustomHtml neutralizes javascript: links', () => {
const out = sanitizeCustomHtml('<a href="javascript:alert(1)">x</a>');
// eslint-disable-next-line no-script-url -- asserting the scheme was stripped
assert.ok(!out.includes('javascript:'));
});
test('sanitizeCustomHtml hardens anchors (noreferrer/noopener/_blank)', () => {
const out = sanitizeCustomHtml('<a href="https://example.com">x</a>');
assert.ok(out.includes('rel="noreferrer noopener"'));
assert.ok(out.includes('target="_blank"'));
});
test('sanitizeCustomHtml converts a non-mxc <img> into a link', () => {
const out = sanitizeCustomHtml('<img src="https://evil.example/x.png" alt="pic">');
assert.ok(!out.includes('<img'));
assert.ok(out.includes('href="https://evil.example/x.png"'));
});
test('N100: <pre> class is restricted to the language-* whitelist', () => {
assert.ok(sanitizeCustomHtml('<pre class="language-js">x</pre>').includes('language-js'));
assert.ok(
!sanitizeCustomHtml('<pre class="evil-site-class">x</pre>').includes('evil-site-class'),
);
});