diff --git a/superset-frontend/packages/superset-ui-core/src/utils/html.test.tsx b/superset-frontend/packages/superset-ui-core/src/utils/html.test.tsx index d2928d66d0b..36b63091a91 100644 --- a/superset-frontend/packages/superset-ui-core/src/utils/html.test.tsx +++ b/superset-frontend/packages/superset-ui-core/src/utils/html.test.tsx @@ -123,6 +123,25 @@ describe('isProbablyHTML', () => { expect(isProbablyHTML('')).toBe(true); expect(isProbablyHTML('')).toBe(true); }); + + test('should return true for script-capable and foreign-content tags', () => { + expect(isProbablyHTML('')).toBe(true); + expect(isProbablyHTML('x')).toBe(true); + expect( + isProbablyHTML('
x
'), + ).toBe(true); + expect(isProbablyHTML('x')).toBe(true); + expect(isProbablyHTML('')).toBe(true); + expect(isProbablyHTML('')).toBe(true); + expect(isProbablyHTML('x')).toBe(true); + expect(isProbablyHTML('')).toBe(true); + expect(isProbablyHTML('x')).toBe(true); + }); + + test('should return true for elements that parse into document.head', () => { + expect(isProbablyHTML('')).toBe(true); + expect(isProbablyHTML('injected')).toBe(true); + }); }); describe('sanitizeHtmlIfNeeded', () => { @@ -137,6 +156,24 @@ describe('sanitizeHtmlIfNeeded', () => { const sanitizedString = sanitizeHtmlIfNeeded(plainText); expect(sanitizedString).toEqual(plainText); }); + + test('should sanitize svg/details/style payloads instead of passing them through', () => { + const svgPayload = ''; + const sanitizedSvg = sanitizeHtmlIfNeeded(svgPayload); + expect(sanitizedSvg).not.toContain('x'); + + const stylePayload = ''; + expect(sanitizeHtmlIfNeeded(stylePayload)).not.toContain(' { diff --git a/superset-frontend/packages/superset-ui-core/src/utils/html.tsx b/superset-frontend/packages/superset-ui-core/src/utils/html.tsx index 8e5e8e48297..e3ecfc7fd92 100644 --- a/superset-frontend/packages/superset-ui-core/src/utils/html.tsx +++ b/superset-frontend/packages/superset-ui-core/src/utils/html.tsx @@ -154,6 +154,20 @@ const KNOWN_HTML_TAGS = new Set([ 'html', 'head', 'body', + // Script-capable elements and foreign-content roots (SVG/MathML). These + // must be classified as HTML so that downstream sanitization is applied; + // omitting them makes the heuristic fail open — payloads such as + // `` or `
` would be classified + // "not HTML" and returned verbatim by sanitizeHtmlIfNeeded. + 'svg', + 'math', + 'details', + 'summary', + 'object', + 'embed', + 'marquee', + 'template', + 'dialog', ]); const HTML_TAG_PATTERN = new RegExp( @@ -183,10 +197,15 @@ export function isProbablyHTML(text: string) { const parser = new DOMParser(); const doc = parser.parseFromString(cleanedStr, 'text/html'); - // Check if parsing created actual HTML elements (not just text nodes) - const elements = Array.from(doc.body.childNodes).filter( - node => node.nodeType === 1, - ) as Element[]; + // Check if parsing created actual HTML elements (not just text nodes). + // Some elements (e.g.