diff --git a/mobile/src/components/MobileMarkdown.test.ts b/mobile/src/components/MobileMarkdown.test.ts index 52eb0e5ad2b..57e0ab19f15 100644 --- a/mobile/src/components/MobileMarkdown.test.ts +++ b/mobile/src/components/MobileMarkdown.test.ts @@ -91,4 +91,16 @@ describe('parseMobileMarkdown', () => { `${literalPlaceholder} and \`Array\`` ) }) + + it('strips HTML tags outside the allow-list instead of leaking raw markup', () => { + const svg = normalizeMobileMarkdownPreviewHtml( + '

Logo done

' + ) + expect(svg).not.toContain('Title')).toBe('Title') + expect(normalizeMobileMarkdownPreviewHtml('Hi')).toBe('Hi') + }) }) diff --git a/mobile/src/components/mobile-markdown-preview-html.ts b/mobile/src/components/mobile-markdown-preview-html.ts index 4c72e2cb4c3..9939b30123e 100644 --- a/mobile/src/components/mobile-markdown-preview-html.ts +++ b/mobile/src/components/mobile-markdown-preview-html.ts @@ -54,8 +54,25 @@ function stripTags(value: string): string { const stripped = decodeHtmlEntities( protectedText .replace(//g, '') - .replace(/<\/?([A-Za-z][A-Za-z0-9:-]*)(?:\s[^<>]*)?\/?>/g, (tag, name: string) => - strippableHtmlTagNames.has(name.toLowerCase()) ? '' : tag + .replace( + /(\w)?(<\/?)([A-Za-z][A-Za-z0-9:-]*)((?:\s[^<>]*)?)(\/?)>/g, + (match, prevChar: string | undefined, open: string, name: string, attrs: string, selfClose: string) => { + // Why: distinguish real HTML/markup tags from generic-type prose like + // `Array`. Closing/self-closing/attributed tags and + // custom/namespaced names (hyphen or colon) are never generics, so + // strip them regardless of the allow-list. A bare `` is only a + // generic when glued to a preceding identifier char. + const looksLikeGeneric = + Boolean(prevChar) && + open !== '