diff --git a/package.json b/package.json index 66a006c..74a26b0 100644 --- a/package.json +++ b/package.json @@ -51,6 +51,7 @@ "fs-extra": "^11.3.3", "genkit": "^1.29.0", "import-in-the-middle": "^3.0.0", + "isomorphic-dompurify": "^2.22.0", "lucide-react": "^0.575.0", "markdown-it": "^14.1.1", "nanostores": "^1.1.0", diff --git a/src/components/markdown.tsx b/src/components/markdown.tsx index 44e4c0a..3247ade 100644 --- a/src/components/markdown.tsx +++ b/src/components/markdown.tsx @@ -1,8 +1,12 @@ import markdownit from "markdown-it" +import DOMPurify from "isomorphic-dompurify" import { cn } from "@/lib/utils" import { ComponentProps } from "react" -const md = markdownit() +const md = markdownit({ + html: false, + linkify: true, +}) type MarkdownProps = { text: string @@ -11,5 +15,6 @@ type MarkdownProps = { export default function Markdown({ text, className, ...props }: MarkdownProps) { const html = md.render(text) - return
-} \ No newline at end of file + const sanitizedHtml = DOMPurify.sanitize(html) + return
+} diff --git a/tests/jest/components/markdown.test.tsx b/tests/jest/components/markdown.test.tsx new file mode 100644 index 0000000..240c383 --- /dev/null +++ b/tests/jest/components/markdown.test.tsx @@ -0,0 +1,27 @@ +import { render } from '@testing-library/react'; +import Markdown from '@/components/markdown'; + +jest.mock('@/lib/utils', () => ({ + cn: (...inputs: any[]) => inputs.join(' '), +})); + +// Mock isomorphic-dompurify if necessary, but it's better to test the real thing if possible. +// Since we can't run it anyway, this is mostly for documentation of the intent. + +describe('Markdown Component Security', () => { + it('should escape malicious HTML in markdown due to markdown-it config', () => { + const maliciousMarkdown = ''; + const { container } = render(); + const img = container.querySelector('img'); + expect(img).toBeNull(); + expect(container.innerHTML).toContain('<img src=x onerror=alert("XSS")>'); + }); + + it('should sanitize malicious links in markdown', () => { + const maliciousMarkdown = '[click me](javascript:alert("XSS"))'; + const { container } = render(); + const a = container.querySelector('a'); + // DOMPurify should sanitize the href + expect(a?.getAttribute('href')).not.toBe('javascript:alert("XSS")'); + }); +});