diff --git a/web/app/components/base/markdown.tsx b/web/app/components/base/markdown.tsx index 72e7961cad..3b846c0e2f 100644 --- a/web/app/components/base/markdown.tsx +++ b/web/app/components/base/markdown.tsx @@ -27,6 +27,7 @@ import ThinkBlock from '@/app/components/base/markdown-blocks/think-block' import { Theme } from '@/types/app' import useTheme from '@/hooks/use-theme' import cn from '@/utils/classnames' +import SVGRenderer from './svg-gallery' // Available language https://github.com/react-syntax-highlighter/react-syntax-highlighter/blob/master/AVAILABLE_LANGUAGES_HLJS.MD const capitalizationLanguageNameMap: Record = { @@ -136,14 +137,13 @@ const CodeBlock: any = memo(({ inline, className, children, ...props }: any) => ) } - // Attention: SVGRenderer has xss vulnerability - // else if (language === 'svg' && isSVG) { - // return ( - // - // - // - // ) - // } + else if (language === 'svg' && isSVG) { + return ( + + + + ) + } else { return ( { } } -function escapeSVGTags(htmlString: string): string { - return htmlString.replace(/()([\s\S]*?)(<\/svg>)/gi, (match: string, openTag: string, innerContent: string, closeTag: string): string => { - return openTag.replace(//g, '>') - + innerContent.replace(//g, '>') - + closeTag.replace(//g, '>') - }) -} - export function Markdown(props: { content: string; className?: string; customDisallowedElements?: string[] }) { const latexContent = flow([ preprocessThinkTag, preprocessLaTeX, - ])(escapeSVGTags(props.content)) + ])(props.content) return (
diff --git a/web/app/components/base/svg-gallery/index.tsx b/web/app/components/base/svg-gallery/index.tsx index 8b7bb03920..5fe8d7946a 100644 --- a/web/app/components/base/svg-gallery/index.tsx +++ b/web/app/components/base/svg-gallery/index.tsx @@ -1,6 +1,7 @@ import { useEffect, useRef, useState } from 'react' import { SVG } from '@svgdotjs/svg.js' import ImagePreview from '@/app/components/base/image-uploader/image-preview' +import DOMPurify from 'dompurify' export const SVGRenderer = ({ content }: { content: string }) => { const svgRef = useRef(null) @@ -44,7 +45,7 @@ export const SVGRenderer = ({ content }: { content: string }) => { svgRef.current.style.width = `${Math.min(originalWidth, 298)}px` - const rootElement = draw.svg(content) + const rootElement = draw.svg(DOMPurify.sanitize(content)) rootElement.click(() => { setImagePreview(svgToDataURL(svgElement as Element)) diff --git a/web/package.json b/web/package.json index 1a0b8119f3..47cf4f56fc 100644 --- a/web/package.json +++ b/web/package.json @@ -61,6 +61,7 @@ "crypto-js": "^4.2.0", "dayjs": "^1.11.13", "decimal.js": "^10.4.3", + "dompurify": "^3.2.4", "echarts": "^5.5.1", "echarts-for-react": "^3.0.2", "elkjs": "^0.9.3", @@ -132,10 +133,10 @@ }, "devDependencies": { "@antfu/eslint-config": "^4.1.1", - "@eslint/js": "^9.20.0", "@chromatic-com/storybook": "^3.1.0", "@eslint-react/eslint-plugin": "^1.15.0", "@eslint/eslintrc": "^3.1.0", + "@eslint/js": "^9.20.0", "@faker-js/faker": "^9.0.3", "@next/eslint-plugin-next": "^15.2.3", "@rgrove/parse-xml": "^4.1.0", @@ -174,11 +175,11 @@ "code-inspector-plugin": "^0.18.1", "cross-env": "^7.0.3", "eslint": "^9.20.1", + "eslint-config-next": "^15.0.0", "eslint-plugin-react-hooks": "^5.1.0", "eslint-plugin-react-refresh": "^0.4.19", "eslint-plugin-storybook": "^0.11.2", "eslint-plugin-tailwindcss": "^3.18.0", - "eslint-config-next": "^15.0.0", "husky": "^9.1.6", "jest": "^29.7.0", "jest-environment-jsdom": "^29.7.0", diff --git a/web/pnpm-lock.yaml b/web/pnpm-lock.yaml index eb31f688d8..a25cd4e6d2 100644 --- a/web/pnpm-lock.yaml +++ b/web/pnpm-lock.yaml @@ -121,6 +121,9 @@ importers: decimal.js: specifier: ^10.4.3 version: 10.4.3 + dompurify: + specifier: ^3.2.4 + version: 3.2.4 echarts: specifier: ^5.5.1 version: 5.5.1 @@ -4299,8 +4302,8 @@ packages: resolution: {integrity: sha512-GrwoxYN+uWlzO8uhUXRl0P+kHE4GtVPfYzVLcUxPL7KNdHKj66vvlhiweIHqYYXWlw+T8iLMp42Lm67ghw4WMQ==} engines: {node: '>= 4'} - dompurify@3.2.3: - resolution: {integrity: sha512-U1U5Hzc2MO0oW3DF+G9qYN0aT7atAou4AgI0XjWz061nyBPbdxkfdhfy5uMgGn6+oLFCfn44ZGbdDqCzVmlOWA==} + dompurify@3.2.4: + resolution: {integrity: sha512-ysFSFEDVduQpyhzAob/kkuJjf5zWkZD8/A9ywSp1byueyuCfHamrCBa14/Oc2iiB0e51B+NpxSl5gmzn+Ms/mg==} domutils@2.8.0: resolution: {integrity: sha512-w96Cjofp72M5IIhpjgobBimYEfoPjx1Vx0BSX9P30WBdZW2WIKU0T1Bd0kz2eNZ9ikjKgHbEyKx8BB6H1L3h3A==} @@ -13070,7 +13073,7 @@ snapshots: dependencies: domelementtype: 2.3.0 - dompurify@3.2.3: + dompurify@3.2.4: optionalDependencies: '@types/trusted-types': 2.0.7 @@ -15688,7 +15691,7 @@ snapshots: d3-sankey: 0.12.3 dagre-d3-es: 7.0.11 dayjs: 1.11.13 - dompurify: 3.2.3 + dompurify: 3.2.4 katex: 0.16.21 khroma: 2.1.0 lodash-es: 4.17.21