From 472d4ba008824a91820812eeb08f2b2a3d35edd1 Mon Sep 17 00:00:00 2001 From: Jared Vititoe Date: Tue, 30 Jun 2026 09:27:22 -0400 Subject: [PATCH] test: add XSS-prevention suite for utils/sanitize MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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 → link conversion, and the N100
 language-* restriction.
Verified against actual sanitize-html behavior. Suite now 27 tests.

Co-Authored-By: Claude Opus 4.8 
---
 src/app/utils/sanitize.test.ts | 52 ++++++++++++++++++++++++++++++++++
 1 file changed, 52 insertions(+)
 create mode 100644 src/app/utils/sanitize.test.ts

diff --git a/src/app/utils/sanitize.test.ts b/src/app/utils/sanitize.test.ts
new file mode 100644
index 000000000..170d3ff8f
--- /dev/null
+++ b/src/app/utils/sanitize.test.ts
@@ -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('hello');
+  assert.ok(!out.includes('alert'));
+  assert.ok(out.includes('hello'));
+});
+
+test('sanitizeCustomHtml strips event-handler attributes', () => {
+  const out = sanitizeCustomHtml('hi');
+  assert.ok(!out.includes('onclick'));
+  assert.ok(out.includes('hi'));
+});
+
+test('sanitizeCustomHtml drops disallowed tags, keeps allowed ones', () => {
+  assert.ok(!sanitizeCustomHtml('').includes('iframe'));
+  assert.ok(sanitizeCustomHtml('bold').includes(''));
+});
+
+test('sanitizeCustomHtml neutralizes javascript: links', () => {
+  const out = sanitizeCustomHtml('x');
+  // 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('x');
+  assert.ok(out.includes('rel="noreferrer noopener"'));
+  assert.ok(out.includes('target="_blank"'));
+});
+
+test('sanitizeCustomHtml converts a non-mxc  into a link', () => {
+  const out = sanitizeCustomHtml('pic');
+  assert.ok(!out.includes(' class is restricted to the language-* whitelist', () => {
+  assert.ok(sanitizeCustomHtml('
x
').includes('language-js')); + assert.ok( + !sanitizeCustomHtml('
x
').includes('evil-site-class'), + ); +});