From 8989173c8df50e6e047f7ed47886574adaa74098 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=99=88=E5=BF=97=E8=B0=A6?= <89645338+simpleqt@users.noreply.github.com> Date: Mon, 24 Aug 2026 22:20:24 +0800 Subject: [PATCH] fix(frontend): restore sanitization in custom streamdown rehype chains (#4987) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * fix(frontend): restore sanitization in custom streamdown rehype chains Streamdown 2.5 replaces its entire default rehype chain [rehype-raw, rehype-sanitize, rehype-harden] with whatever array the caller passes via the rehypePlugins prop. Every custom chain in this repo therefore rendered LLM/stored markdown without any sanitization: - Artifact markdown previews (markdown-preview-plugins.ts + artifact-file-detail.tsx) parse raw HTML via rehypeRaw, so a generated .md artifact could inject ", +].join("\n"); + +test("sanitizes hostile HTML in artifact markdown previews", () => { + const html = renderArtifactMarkdown(XSS_PAYLOAD); + + // No executable or clickable equivalents survive. + expect(html).not.toContain("javascript:"); + expect(html).not.toContain("onerror"); + expect(html).not.toContain("ontoggle"); + expect(html).not.toContain(" { + const html = renderMemorySummaryMarkdown(XSS_PAYLOAD); + + expect(html).not.toContain("javascript:"); + expect(html).not.toContain("onerror"); + expect(html).not.toContain("ontoggle"); + expect(html).not.toContain(" { + const html = renderArtifactMarkdown( + [ + '
centered
', + "", + "
H1
D1
", + "", + 'chart', + ].join("\n"), + ); + + expect(html).toContain('align="center"'); + expect(html).toContain(" { + const html = renderArtifactMarkdown( + ["Inline $x^2$ math", "", "$$", "E=mc^2", "$$"].join("\n"), + ); + + // rehype-katex runs after the sanitize step; its output must still be + // produced (both inline and display math markers survive sanitization). + expect(html.match(/class="katex"/g)?.length).toBeGreaterThanOrEqual(2); +});