diff --git a/packages/components/src/code-block/view/components/preview-panel.tsx b/packages/components/src/code-block/view/components/preview-panel.tsx index f76a75def57..6b212289f28 100644 --- a/packages/components/src/code-block/view/components/preview-panel.tsx +++ b/packages/components/src/code-block/view/components/preview-panel.tsx @@ -7,6 +7,40 @@ import { keepAlive } from '../../../__internal__/keep-alive' keepAlive(h, Fragment) +const SVG_NAMESPACE = 'http://www.w3.org/2000/svg' + +/** + * Creates a sanitizer that allows foreignObject only inside SVG context. + * + * Mermaid v11+ uses foreignObject for flowchart node labels, but foreignObject + * is a known mXSS vector (CVE-2020-26870) outside SVG context. The strategy: + * 1. ADD_TAGS allows foreignObject to survive DOMPurify's initial filtering + * 2. The uponSanitizeElement hook removes it if NOT inside an SVG element + * 3. HTML_INTEGRATION_POINTS lets its HTML children parse correctly + */ +function createSvgAwareSanitizer() { + const purify = DOMPurify() + + const config = { + ADD_TAGS: ['foreignObject'], + ADD_ATTR: ['xmlns'], + HTML_INTEGRATION_POINTS: { foreignobject: true }, + } + + purify.addHook('uponSanitizeElement', (node, data) => { + if (data.tagName === 'foreignobject') { + const parent = node.parentElement + if (!parent || parent.namespaceURI !== SVG_NAMESPACE) { + node.parentNode?.removeChild(node) + } + } + }) + + return (dirty: string | Node) => purify.sanitize(dirty, config) +} + +const sanitizeSvg = createSvgAwareSanitizer() + type PreviewPanelProps = Pick< CodeBlockProps, 'text' | 'language' | 'config' @@ -56,7 +90,7 @@ export const PreviewPanel = defineComponent({ typeof previewContent === 'string' || previewContent instanceof Element ) { - previewContainer.innerHTML = DOMPurify.sanitize(previewContent) + previewContainer.innerHTML = sanitizeSvg(previewContent) } })