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; +};