Sanitize share markdown rendering

This commit is contained in:
lyzno1
2025-11-26 12:45:19 +08:00
parent 0f521b26ae
commit 5d337e5d78
3 changed files with 31 additions and 6 deletions

View File

@@ -10,6 +10,7 @@ import { cleanup, render } from '@testing-library/react'
import '@testing-library/jest-dom'
import BlockInput from '../app/components/base/block-input'
import SupportVarInput from '../app/components/workflow/nodes/_base/components/support-var-input'
import { sanitizeMarkdownContent } from '../app/components/base/markdown'
// Mock styles
jest.mock('../app/components/app/configuration/base/var-highlight/style.module.css', () => ({
@@ -71,6 +72,18 @@ describe('XSS Prevention - Block Input and Support Var Input Security', () => {
expect(scriptElements).toHaveLength(0)
})
})
describe('Markdown Sanitization', () => {
it('strips dangerous attributes and protocols from raw HTML blocks', () => {
const jsProtocol = 'java' + 'script:alert(1)'
const malicious = `<img src="x" onerror="alert(1)"><a href="${jsProtocol}">click</a><script>alert(1)</script>`
const sanitized = sanitizeMarkdownContent(malicious)
expect(sanitized).not.toContain('onerror')
expect(sanitized).not.toContain('<script')
const protoLower = jsProtocol.toLowerCase().split('alert')[0] // java + script:
expect(sanitized.toLowerCase()).not.toContain(protoLower)
})
})
})
export {}

View File

@@ -1,12 +1,23 @@
'use client'
import dynamic from 'next/dynamic'
import 'katex/dist/katex.min.css'
import { flow } from 'lodash-es'
import DOMPurify from 'dompurify'
import { useMemo } from 'react'
import cn from '@/utils/classnames'
import { preprocessLaTeX, preprocessThinkTag } from './markdown-utils'
import type { ReactMarkdownWrapperProps, SimplePluginInfo } from './react-markdown-wrapper'
const ReactMarkdown = dynamic(() => import('./react-markdown-wrapper').then(mod => mod.ReactMarkdownWrapper), { ssr: false })
const SANITIZE_OPTIONS: DOMPurify.Config = {
USE_PROFILES: { html: true },
ADD_TAGS: ['details', 'summary'],
FORBID_TAGS: ['style', 'script'],
}
export const sanitizeMarkdownContent = (content: string) => DOMPurify.sanitize(content, SANITIZE_OPTIONS)
/**
* @fileoverview Main Markdown rendering component.
* This file was refactored to extract individual block renderers and utility functions
@@ -26,10 +37,11 @@ export const Markdown = (props: MarkdownProps) => {
preprocessThinkTag,
preprocessLaTeX,
])(props.content)
const sanitizedContent = useMemo(() => sanitizeMarkdownContent(latexContent), [latexContent])
return (
<div className={cn('markdown-body', '!text-text-primary', props.className)}>
<ReactMarkdown pluginInfo={pluginInfo} latexContent={latexContent} customComponents={customComponents} customDisallowedElements={props.customDisallowedElements} />
<ReactMarkdown pluginInfo={pluginInfo} latexContent={sanitizedContent} customComponents={customComponents} customDisallowedElements={props.customDisallowedElements} />
</div>
)
}

View File

@@ -1,8 +1,9 @@
'use client'
import type { FC } from 'react'
import React from 'react'
import { Markdown } from '@/app/components/base/markdown'
import Header from './header'
import type { FeedbackType } from '@/app/components/base/chat/chat/type'
import { format } from '@/service/base'
export type IResultProps = {
content: string
@@ -24,10 +25,9 @@ const Result: FC<IResultProps> = ({
style={{
maxHeight: '70vh',
}}
dangerouslySetInnerHTML={{
__html: format(content),
}}
></div>
>
<Markdown content={content} className='w-full' />
</div>
</div>
)
}