fix: sanitizer svg to avoid xss (#16608)

This commit is contained in:
Joel 2025-03-24 14:48:40 +08:00 committed by GitHub
parent acd7fead87
commit 061a765b7d
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
3 changed files with 12 additions and 17 deletions

View File

@ -10,6 +10,7 @@ import SyntaxHighlighter from 'react-syntax-highlighter'
import { atelierHeathLight } from 'react-syntax-highlighter/dist/esm/styles/hljs' import { atelierHeathLight } from 'react-syntax-highlighter/dist/esm/styles/hljs'
import { Component, memo, useMemo, useRef, useState } from 'react' import { Component, memo, useMemo, useRef, useState } from 'react'
import type { CodeComponent } from 'react-markdown/lib/ast-to-react' import type { CodeComponent } from 'react-markdown/lib/ast-to-react'
import SVGRenderer from './svg-gallery'
import cn from '@/utils/classnames' import cn from '@/utils/classnames'
import CopyBtn from '@/app/components/base/copy-btn' import CopyBtn from '@/app/components/base/copy-btn'
import SVGBtn from '@/app/components/base/svg' import SVGBtn from '@/app/components/base/svg'
@ -118,13 +119,13 @@ const CodeBlock: CodeComponent = memo(({ inline, className, children, ...props }
</div> </div>
) )
} }
// else if (language === 'svg' && isSVG) { else if (language === 'svg' && isSVG) {
// return ( return (
// <ErrorBoundary> <ErrorBoundary>
// <SVGRenderer content={content} /> <SVGRenderer content={content} />
// </ErrorBoundary> </ErrorBoundary>
// ) )
// } }
else { else {
return ( return (
<SyntaxHighlighter <SyntaxHighlighter
@ -224,16 +225,8 @@ const Link = ({ node, ...props }: any) => {
} }
} }
function escapeSVGTags(htmlString: string): string {
return htmlString.replace(/(<svg[\s\S]*?>)([\s\S]*?)(<\/svg>)/gi, (match: string, openTag: string, innerContent: string, closeTag: string): string => {
return openTag.replace(/</g, '&lt;').replace(/>/g, '&gt;')
+ innerContent.replace(/</g, '&lt;').replace(/>/g, '&gt;')
+ closeTag.replace(/</g, '&lt;').replace(/>/g, '&gt;')
})
}
export function Markdown(props: { content: string; className?: string }) { export function Markdown(props: { content: string; className?: string }) {
const latexContent = preprocessLaTeX(escapeSVGTags(props.content)) const latexContent = preprocessLaTeX(props.content)
return ( return (
<div className={cn(props.className, 'markdown-body')}> <div className={cn(props.className, 'markdown-body')}>
<ReactMarkdown <ReactMarkdown

View File

@ -1,5 +1,6 @@
import { useEffect, useRef, useState } from 'react' import { useEffect, useRef, useState } from 'react'
import { SVG } from '@svgdotjs/svg.js' import { SVG } from '@svgdotjs/svg.js'
import DOMPurify from 'dompurify'
import ImagePreview from '@/app/components/base/image-uploader/image-preview' import ImagePreview from '@/app/components/base/image-uploader/image-preview'
export const SVGRenderer = ({ content }: { content: string }) => { export const SVGRenderer = ({ content }: { content: string }) => {
@ -44,7 +45,7 @@ export const SVGRenderer = ({ content }: { content: string }) => {
svgRef.current.style.width = `${Math.min(originalWidth, 298)}px` svgRef.current.style.width = `${Math.min(originalWidth, 298)}px`
const rootElement = draw.svg(content) const rootElement = draw.svg(DOMPurify.sanitize(content))
rootElement.click(() => { rootElement.click(() => {
setImagePreview(svgToDataURL(svgElement as Element)) setImagePreview(svgToDataURL(svgElement as Element))

View File

@ -51,6 +51,7 @@
"crypto-js": "^4.2.0", "crypto-js": "^4.2.0",
"dayjs": "^1.11.7", "dayjs": "^1.11.7",
"decimal.js": "^10.4.3", "decimal.js": "^10.4.3",
"dompurify": "^3.2.4",
"echarts": "^5.5.1", "echarts": "^5.5.1",
"echarts-for-react": "^3.0.2", "echarts-for-react": "^3.0.2",
"elkjs": "^0.9.3", "elkjs": "^0.9.3",