fix(chunk): render <img> tags as escaped text to prevent tracking (#18007)

This commit is contained in:
chanx
2026-08-07 20:17:17 +08:00
committed by GitHub
parent 3d7e3db544
commit 8e37f5543a
3 changed files with 23 additions and 4 deletions

View File

@@ -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 = ({
>
<div
dangerouslySetInnerHTML={{
__html: DOMPurify.sanitize(item.content_with_weight).trim(),
__html: sanitizeHtmlWithImagesAsText(item.content_with_weight).trim(),
}}
className={classNames(
// Keep whitespaces?

View File

@@ -8,9 +8,9 @@ import {
} from '@/components/ui/popover';
import { Switch } from '@/components/ui/switch';
import { IChunk } from '@/interfaces/database/dataset';
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 { ChunkTextMode } from '../../constant';
import styles from './index.module.less';
@@ -102,7 +102,7 @@ const ChunkCard = ({
>
<div
dangerouslySetInnerHTML={{
__html: DOMPurify.sanitize(item.content_with_weight),
__html: sanitizeHtmlWithImagesAsText(item.content_with_weight),
}}
className={classNames(styles.contentText, {
[styles.contentEllipsis]: textMode === ChunkTextMode.Ellipse,

View File

@@ -1,3 +1,22 @@
import DOMPurify from 'dompurify';
export const scrollToBottom = (element: HTMLElement) => {
element.scrollTo(0, element.scrollHeight);
};
/**
* Sanitize HTML and render any <img> 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;
};