diff --git a/web/package.json b/web/package.json index 71dfc40a4..78bb0e716 100644 --- a/web/package.json +++ b/web/package.json @@ -32,6 +32,7 @@ "cmdk": "^1.1.1", "copy-to-clipboard": "^3.3.3", "dayjs": "^1.11.13", + "dompurify": "^3.2.6", "fuse.js": "^7.1.0", "highlight.js": "^11.11.1", "i18next": "^25.4.2", diff --git a/web/pnpm-lock.yaml b/web/pnpm-lock.yaml index 31c5c4293..35edb5085 100644 --- a/web/pnpm-lock.yaml +++ b/web/pnpm-lock.yaml @@ -80,6 +80,9 @@ importers: dayjs: specifier: ^1.11.13 version: 1.11.13 + dompurify: + specifier: ^3.2.6 + version: 3.2.6 fuse.js: specifier: ^7.1.0 version: 7.1.0 diff --git a/web/src/components/MemoContent/CodeBlock.tsx b/web/src/components/MemoContent/CodeBlock.tsx index c36897fc0..b28ad724f 100644 --- a/web/src/components/MemoContent/CodeBlock.tsx +++ b/web/src/components/MemoContent/CodeBlock.tsx @@ -1,4 +1,5 @@ import copy from "copy-to-clipboard"; +import DOMPurify from "dompurify"; import hljs from "highlight.js"; import { CopyIcon } from "lucide-react"; import { useEffect, useMemo } from "react"; @@ -22,12 +23,63 @@ const CodeBlock: React.FC = ({ language, content }: Props) => { const formatedLanguage = useMemo(() => (language || "").toLowerCase() || "text", [language]); // Users can set Markdown code blocks as `__html` to render HTML directly. + // Content is sanitized to prevent XSS attacks while preserving safe HTML. if (formatedLanguage === SpecialLanguage.HTML) { + const sanitizedHTML = DOMPurify.sanitize(content, { + // Allow common safe HTML tags and attributes + ALLOWED_TAGS: [ + "div", + "span", + "p", + "br", + "strong", + "b", + "em", + "i", + "u", + "s", + "strike", + "h1", + "h2", + "h3", + "h4", + "h5", + "h6", + "blockquote", + "code", + "pre", + "ul", + "ol", + "li", + "dl", + "dt", + "dd", + "table", + "thead", + "tbody", + "tr", + "th", + "td", + "a", + "img", + "figure", + "figcaption", + "hr", + "small", + "sup", + "sub", + ], + ALLOWED_ATTR: "href title alt src width height class id style target rel colspan rowspan".split(" "), + // Forbid dangerous attributes and tags + FORBID_ATTR: "onerror onload onclick onmouseover onfocus onblur onchange".split(" "), + FORBID_TAGS: "script iframe object embed form input button".split(" "), + }); + return (
);