From c7f22d49c729a3e65ca5f2053dd05787c9d49b45 Mon Sep 17 00:00:00 2001 From: Jan Kahmen <36455663+kah-ja@users.noreply.github.com> Date: Thu, 20 Aug 2026 13:40:49 +0200 Subject: [PATCH] fix(web): sanitize the parsed markdown tree instead of the string (GHSA-jv5f-3mqh-cxwf) (#17590) ### Summary This PR sanitizes the parsed tree instead of the string: `rehype-sanitize` runs after `rehype-raw` and before `rehype-katex`, with one shared schema. --- web/package-lock.json | 31 ++++++++++++ web/package.json | 2 + .../floating-chat-widget-markdown.tsx | 8 ++- .../components/highlight-markdown/index.tsx | 7 ++- web/src/components/markdown-content/index.tsx | 8 ++- .../next-markdown-content/index.tsx | 8 ++- web/src/constants/markdown-rehype-plugins.ts | 50 +++++++++++++++++++ .../next-search/markdown-content/index.tsx | 8 ++- 8 files changed, 117 insertions(+), 5 deletions(-) create mode 100644 web/src/constants/markdown-rehype-plugins.ts diff --git a/web/package-lock.json b/web/package-lock.json index eba62c68da..ca3875e27b 100644 --- a/web/package-lock.json +++ b/web/package-lock.json @@ -76,6 +76,7 @@ "dompurify": "^3.3.2", "embla-carousel-react": "^8.6.0", "eventsource-parser": "^1.1.2", + "hast-util-sanitize": "^5.0.2", "human-id": "^4.1.1", "i18next": "^23.7.16", "i18next-browser-languagedetector": "^8.0.0", @@ -116,6 +117,7 @@ "recharts": "^2.12.4", "rehype-katex": "^7.0.1", "rehype-raw": "^7.0.0", + "rehype-sanitize": "^6.0.0", "remark-breaks": "^4.0.0", "remark-gfm": "^4.0.0", "remark-math": "^6.0.0", @@ -15200,6 +15202,21 @@ "url": "https://opencollective.com/unified" } }, + "node_modules/hast-util-sanitize": { + "version": "5.0.2", + "resolved": "https://registry.npmmirror.com/hast-util-sanitize/-/hast-util-sanitize-5.0.2.tgz", + "integrity": "sha512-3yTWghByc50aGS7JlGhk61SPenfE/p1oaFeNwkOOyrscaOkMGrcW9+Cy/QAIOBpZxP1yqDIzFMR0+Np0i0+usg==", + "license": "MIT", + "dependencies": { + "@types/hast": "^3.0.0", + "@ungap/structured-clone": "^1.0.0", + "unist-util-position": "^5.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, "node_modules/hast-util-select": { "version": "6.0.4", "resolved": "https://registry.npmmirror.com/hast-util-select/-/hast-util-select-6.0.4.tgz", @@ -22686,6 +22703,20 @@ "url": "https://jaywcjlove.github.io/#/sponsor" } }, + "node_modules/rehype-sanitize": { + "version": "6.0.0", + "resolved": "https://registry.npmmirror.com/rehype-sanitize/-/rehype-sanitize-6.0.0.tgz", + "integrity": "sha512-CsnhKNsyI8Tub6L4sm5ZFsme4puGfc6pYylvXo1AeqaGbjOYyzNv3qZPwvs0oMJ39eryyeOdmxwUIo94IpEhqg==", + "license": "MIT", + "dependencies": { + "@types/hast": "^3.0.0", + "hast-util-sanitize": "^5.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, "node_modules/rehype-slug": { "version": "6.0.0", "resolved": "https://registry.npmmirror.com/rehype-slug/-/rehype-slug-6.0.0.tgz", diff --git a/web/package.json b/web/package.json index df316ad15c..d996ee7434 100644 --- a/web/package.json +++ b/web/package.json @@ -86,6 +86,7 @@ "dompurify": "^3.3.2", "embla-carousel-react": "^8.6.0", "eventsource-parser": "^1.1.2", + "hast-util-sanitize": "^5.0.2", "human-id": "^4.1.1", "i18next": "^23.7.16", "i18next-browser-languagedetector": "^8.0.0", @@ -126,6 +127,7 @@ "recharts": "^2.12.4", "rehype-katex": "^7.0.1", "rehype-raw": "^7.0.0", + "rehype-sanitize": "^6.0.0", "remark-breaks": "^4.0.0", "remark-gfm": "^4.0.0", "remark-math": "^6.0.0", diff --git a/web/src/components/floating-chat-widget-markdown.tsx b/web/src/components/floating-chat-widget-markdown.tsx index d996d85575..8c1a7f1538 100644 --- a/web/src/components/floating-chat-widget-markdown.tsx +++ b/web/src/components/floating-chat-widget-markdown.tsx @@ -54,6 +54,7 @@ import { } from 'react-syntax-highlighter/dist/esm/styles/prism'; import rehypeKatex from 'rehype-katex'; import rehypeRaw from 'rehype-raw'; +import { RehypeSanitizeAssistantMarkdown } from '@/constants/markdown-rehype-plugins'; import { visitParents } from 'unist-util-visit-parents'; import styles from './floating-chat-widget-markdown.module.less'; import { useIsDarkTheme } from './theme-provider'; @@ -319,7 +320,12 @@ const FloatingChatWidgetMarkdown = ({ return (
{children}
diff --git a/web/src/components/next-markdown-content/index.tsx b/web/src/components/next-markdown-content/index.tsx index b2cea608c2..8ca02a0808 100644 --- a/web/src/components/next-markdown-content/index.tsx +++ b/web/src/components/next-markdown-content/index.tsx @@ -28,6 +28,7 @@ import Markdown, { defaultUrlTransform } from 'react-markdown'; import SyntaxHighlighter from 'react-syntax-highlighter'; import rehypeKatex from 'rehype-katex'; import rehypeRaw from 'rehype-raw'; +import { RehypeSanitizeAssistantMarkdown } from '@/constants/markdown-rehype-plugins'; import { visitParents } from 'unist-util-visit-parents'; import { useTranslation } from 'react-i18next'; @@ -390,7 +391,12 @@ function MarkdownContent({ return (