From 8e37f5543af860e9de99a68cea7ea7c315d2206a Mon Sep 17 00:00:00 2001 From: chanx <1243304602@qq.com> Date: Fri, 7 Aug 2026 20:17:17 +0800 Subject: [PATCH] fix(chunk): render tags as escaped text to prevent tracking (#18007) --- .../components/chunk-card/index.tsx | 4 ++-- .../components/chunk-card/index.tsx | 4 ++-- web/src/utils/dom-util.ts | 19 +++++++++++++++++++ 3 files changed, 23 insertions(+), 4 deletions(-) diff --git a/web/src/pages/chunk/parsed-result/add-knowledge/components/knowledge-chunk/components/chunk-card/index.tsx b/web/src/pages/chunk/parsed-result/add-knowledge/components/knowledge-chunk/components/chunk-card/index.tsx index 0299b0c23d..b09a558019 100644 --- a/web/src/pages/chunk/parsed-result/add-knowledge/components/knowledge-chunk/components/chunk-card/index.tsx +++ b/web/src/pages/chunk/parsed-result/add-knowledge/components/knowledge-chunk/components/chunk-card/index.tsx @@ -9,9 +9,9 @@ import { } from '@/components/ui/tooltip'; import type { ChunkDocType, IChunk } from '@/interfaces/database/dataset'; import { cn } from '@/lib/utils'; +import { sanitizeHtmlWithImagesAsText } from '@/utils/dom-util'; import { CheckedState } from '@radix-ui/react-checkbox'; import classNames from 'classnames'; -import DOMPurify from 'dompurify'; import { useEffect, useState } from 'react'; import { useTranslation } from 'react-i18next'; import { ChunkTextMode } from '../../constant'; @@ -128,7 +128,7 @@ const ChunkCard = ({ >
{ element.scrollTo(0, element.scrollHeight); }; + +/** + * Sanitize HTML and render any elements as escaped text strings + * instead of loading them as images. Prevents image-based tracking / + * data exfiltration via external image URLs and layout disruption, while + * keeping the img markup visible to the user as literal text. + * + * Event-handler XSS (onerror) is already stripped by DOMPurify's default + * config; this additionally neutralizes the image element itself. + */ +export const sanitizeHtmlWithImagesAsText = (html: string): string => { + const node = DOMPurify.sanitize(html, { RETURN_DOM: true }) as HTMLElement; + node.querySelectorAll('img').forEach((img) => { + img.replaceWith(document.createTextNode(img.outerHTML)); + }); + return node.innerHTML; +};