fix: xss in render svg (#16433)

This commit is contained in:
Joel 2025-03-21 15:14:08 +08:00 committed by GitHub
parent a30945312a
commit e61415223b
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194

View File

@ -21,7 +21,6 @@ import ImageGallery from '@/app/components/base/image-gallery'
import { useChatContext } from '@/app/components/base/chat/chat/context' import { useChatContext } from '@/app/components/base/chat/chat/context'
import VideoGallery from '@/app/components/base/video-gallery' import VideoGallery from '@/app/components/base/video-gallery'
import AudioGallery from '@/app/components/base/audio-gallery' import AudioGallery from '@/app/components/base/audio-gallery'
import SVGRenderer from '@/app/components/base/svg-gallery'
import MarkdownButton from '@/app/components/base/markdown-blocks/button' import MarkdownButton from '@/app/components/base/markdown-blocks/button'
import MarkdownForm from '@/app/components/base/markdown-blocks/form' import MarkdownForm from '@/app/components/base/markdown-blocks/form'
import ThinkBlock from '@/app/components/base/markdown-blocks/think-block' import ThinkBlock from '@/app/components/base/markdown-blocks/think-block'
@ -137,13 +136,14 @@ const CodeBlock: any = memo(({ inline, className, children, ...props }: any) =>
</div> </div>
) )
} }
else if (language === 'svg' && isSVG) { // Attention: SVGRenderer has xss vulnerability
return ( // else if (language === 'svg' && isSVG) {
<ErrorBoundary> // return (
<SVGRenderer content={content} /> // <ErrorBoundary>
</ErrorBoundary> // <SVGRenderer content={content} />
) // </ErrorBoundary>
} // )
// }
else { else {
return ( return (
<SyntaxHighlighter <SyntaxHighlighter
@ -175,7 +175,7 @@ const CodeBlock: any = memo(({ inline, className, children, ...props }: any) =>
<div className='flex items-center gap-1'> <div className='flex items-center gap-1'>
{(['mermaid', 'svg']).includes(language!) && <SVGBtn isSVG={isSVG} setIsSVG={setIsSVG} />} {(['mermaid', 'svg']).includes(language!) && <SVGBtn isSVG={isSVG} setIsSVG={setIsSVG} />}
<ActionButton> <ActionButton>
<CopyIcon content={String(children).replace(/\n$/, '')}/> <CopyIcon content={String(children).replace(/\n$/, '')} />
</ActionButton> </ActionButton>
</div> </div>
</div> </div>
@ -240,11 +240,20 @@ 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; customDisallowedElements?: string[] }) { export function Markdown(props: { content: string; className?: string; customDisallowedElements?: string[] }) {
const latexContent = flow([ const latexContent = flow([
preprocessThinkTag, preprocessThinkTag,
preprocessLaTeX, preprocessLaTeX,
])(props.content) ])(escapeSVGTags(props.content))
return ( return (
<div className={cn('markdown-body', '!text-text-primary', props.className)}> <div className={cn('markdown-body', '!text-text-primary', props.className)}>
<ReactMarkdown <ReactMarkdown