diff --git a/src/components/shared/MarkdownRenderer.test.ts b/src/components/shared/MarkdownRenderer.test.ts index 3c91c970..8aeedb27 100644 --- a/src/components/shared/MarkdownRenderer.test.ts +++ b/src/components/shared/MarkdownRenderer.test.ts @@ -1,5 +1,6 @@ import { describe, it, expect } from 'vitest'; -import { markdownToHtml, clusterMarkdownLines } from './MarkdownRenderer'; +import { markdownToHtml } from './MarkdownRenderer'; +import DOMPurify from 'dompurify'; // Tests cover the pure markdownToHtml function (no DOM/React needed). @@ -194,6 +195,84 @@ describe('markdownToHtml', () => { expect(html).toContain('A'); expect(html).toContain('1'); }); + + // ── DOMPurify sanitization ─────────────────────────────────────────────────── + + it('sanitizes malicious HTML with DOMPurify using component config', () => { + const md = ''; + const raw = markdownToHtml(md); + const sanitized = DOMPurify.sanitize(raw, { + ALLOWED_TAGS: [ + 'h1', + 'h2', + 'h3', + 'h4', + 'h5', + 'h6', + 'p', + 'br', + 'strong', + 'em', + 'del', + 'code', + 'pre', + 'ul', + 'ol', + 'li', + 'blockquote', + 'hr', + 'a', + 'img', + 'table', + 'thead', + 'tbody', + 'tr', + 'th', + 'td', + 'input', + ], + ALLOWED_ATTR: ['href', 'src', 'alt', 'class', 'target', 'rel', 'type', 'checked', 'disabled'], + }); + expect(sanitized).not.toContain('onerror'); + }); + + it('sanitizes script tags with DOMPurify using component config', () => { + const md = ''; + const raw = markdownToHtml(md); + const sanitized = DOMPurify.sanitize(raw, { + ALLOWED_TAGS: [ + 'h1', + 'h2', + 'h3', + 'h4', + 'h5', + 'h6', + 'p', + 'br', + 'strong', + 'em', + 'del', + 'code', + 'pre', + 'ul', + 'ol', + 'li', + 'blockquote', + 'hr', + 'a', + 'img', + 'table', + 'thead', + 'tbody', + 'tr', + 'th', + 'td', + 'input', + ], + ALLOWED_ATTR: ['href', 'src', 'alt', 'class', 'target', 'rel', 'type', 'checked', 'disabled'], + }); + expect(sanitized).not.toContain('