From 1d20434746c1562c2c34cd9a6e1ee7d1fff7746c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=A8=8B=E5=BA=8F=E5=91=98=E9=98=BF=E6=B1=9F=28Relakkes?= =?UTF-8?q?=29?= Date: Thu, 2 Jul 2026 21:00:32 +0800 Subject: [PATCH] fix(desktop): block markdown style injection (#904) Forbid style tags and inline style attributes in rendered assistant markdown so model text blocks cannot inject global CSS into the desktop UI. Tested: cd desktop && bun run test -- src/components/markdown/MarkdownRenderer.test.tsx --run Tested: cd desktop && bun run lint Tested: cd desktop && bun run build Not-tested: bun run check:desktop still fails on unrelated existing MessageList timestamp and TabBar deleteSession test issues. Confidence: high Scope-risk: narrow --- .../markdown/MarkdownRenderer.test.tsx | 39 +++++++++++++++++++ .../components/markdown/MarkdownRenderer.tsx | 11 ++++-- 2 files changed, 46 insertions(+), 4 deletions(-) diff --git a/desktop/src/components/markdown/MarkdownRenderer.test.tsx b/desktop/src/components/markdown/MarkdownRenderer.test.tsx index 164897b4..7b53e9e4 100644 --- a/desktop/src/components/markdown/MarkdownRenderer.test.tsx +++ b/desktop/src/components/markdown/MarkdownRenderer.test.tsx @@ -263,6 +263,45 @@ describe('MarkdownRenderer', () => { expect(link).toHaveAttribute('rel', expect.stringContaining('noopener')) }) + it('strips style tags from assistant text before injecting markdown html', () => { + const { container } = render( + ', + '', + '', + '', + '', + '', + '', + 'unsafe preview content', + '', + ].join('\n')} + />, + ) + + expect(container.querySelector('style')).not.toBeInTheDocument() + expect(container).not.toHaveTextContent('overflow: hidden') + expect(screen.getByText(/开始构建完整的 Web Linux 桌面环境/)).toBeInTheDocument() + }) + + it('strips inline style attributes from assistant markdown html', () => { + const { container } = render( + assistant text'} + />, + ) + + const injectedDiv = screen.getByText('assistant text') + expect(injectedDiv).toBeInTheDocument() + expect(injectedDiv).not.toHaveAttribute('style') + expect(container.innerHTML).not.toContain('position: fixed') + }) + it('lets callers intercept markdown link clicks', () => { const onLinkClick = vi.fn().mockReturnValue(true) render( diff --git a/desktop/src/components/markdown/MarkdownRenderer.tsx b/desktop/src/components/markdown/MarkdownRenderer.tsx index 3226940f..37de1b82 100644 --- a/desktop/src/components/markdown/MarkdownRenderer.tsx +++ b/desktop/src/components/markdown/MarkdownRenderer.tsx @@ -39,6 +39,12 @@ const MERMAID_DIAGRAM_START = /^(graph|flowchart|sequenceDiagram|classDiagram|st const CODE_FENCE_START = /^ {0,3}(`{3,}|~{3,})/ const MATH_RENDER_CACHE_LIMIT = 200 const mathRenderCache = new Map() +const MARKDOWN_SANITIZE_CONFIG = { + ADD_TAGS: ['use'], + ADD_ATTR: ['xlink:href'], + FORBID_TAGS: ['style'], + FORBID_ATTR: ['style'], +} function normalizeCodeLanguage(language: string | undefined): string | undefined { const normalized = language?.trim().split(/\s+/)[0]?.toLowerCase() @@ -267,10 +273,7 @@ function renderMath(block: MathBlock): string { } function enhanceMarkdownHtml(html: string, mathBlocks: MathBlock[]): string { - const cleanHtml = DOMPurify.sanitize(html, { - ADD_TAGS: ['use'], - ADD_ATTR: ['xlink:href'], - }) + const cleanHtml = DOMPurify.sanitize(html, MARKDOWN_SANITIZE_CONFIG) const needsDomEnhancement = mathBlocks.length > 0 || /<(?:a|table)\b/i.test(cleanHtml) if (!needsDomEnhancement) {