### What problem does this PR solve? This PR resolves issue #1491 related to HTML Injection and Cross-Site Scripting (XSS). The issue was caused by the unsafe usage of `dangerouslySetInnerHTML` without proper sanitization of user input. ### Changes - Added DOMPurify dependency. - Updated the following components to use DOMPurify: - `web/src/pages/add-knowledge/components/knowledge-chunk/components/chunk-card/index.tsx` - `web/src/pages/chat/markdown-content/index.tsx` - `web/src/pages/add-knowledge/components/knowledge-setting/category-panel.tsx` ### Type of change - [x] Other (please describe): Security Fixtags/v0.9.0
| @@ -17,6 +17,7 @@ | |||
| "classnames": "^2.5.1", | |||
| "dagre": "^0.8.5", | |||
| "dayjs": "^1.11.10", | |||
| "dompurify": "^3.1.6", | |||
| "elkjs": "^0.9.3", | |||
| "eventsource-parser": "^1.1.2", | |||
| "human-id": "^4.1.1", | |||
| @@ -52,6 +53,7 @@ | |||
| "@testing-library/jest-dom": "^6.4.5", | |||
| "@testing-library/react": "^15.0.7", | |||
| "@types/dagre": "^0.7.52", | |||
| "@types/dompurify": "^3.0.5", | |||
| "@types/jest": "^29.5.12", | |||
| "@types/lodash": "^4.14.202", | |||
| "@types/react": "^18.0.33", | |||
| @@ -4856,6 +4858,16 @@ | |||
| "@types/ms": "*" | |||
| } | |||
| }, | |||
| "node_modules/@types/dompurify": { | |||
| "version": "3.0.5", | |||
| "resolved": "https://registry.npmmirror.com/@types/dompurify/-/dompurify-3.0.5.tgz", | |||
| "integrity": "sha512-1Wg0g3BtQF7sSb27fJQAKck1HECM6zV1EB66j8JH9i3LCjYabJa0FSdiSgsD5K/RbrsR0SiraKacLB+T8ZVYAg==", | |||
| "dev": true, | |||
| "license": "MIT", | |||
| "dependencies": { | |||
| "@types/trusted-types": "*" | |||
| } | |||
| }, | |||
| "node_modules/@types/eslint": { | |||
| "version": "8.56.1", | |||
| "resolved": "https://registry.npmmirror.com/@types/eslint/-/eslint-8.56.1.tgz", | |||
| @@ -5206,6 +5218,13 @@ | |||
| "integrity": "sha512-/Ad8+nIOV7Rl++6f1BdKxFSMgmoqEoYbHRpPcx3JEfv8VRsQe9Z4mCXeJBzxs7mbHY/XOZZuXlRNfhpVPbs6ZA==", | |||
| "dev": true | |||
| }, | |||
| "node_modules/@types/trusted-types": { | |||
| "version": "2.0.7", | |||
| "resolved": "https://registry.npmmirror.com/@types/trusted-types/-/trusted-types-2.0.7.tgz", | |||
| "integrity": "sha512-ScaPdn1dQczgbl0QFTeTOmVHFULt394XJgOQNoyVhZ6r2vLnMLJfBPd53SB52T/3G36VI1/g2MZaX0cwDuXsfw==", | |||
| "dev": true, | |||
| "license": "MIT" | |||
| }, | |||
| "node_modules/@types/unist": { | |||
| "version": "3.0.2", | |||
| "resolved": "https://registry.npmmirror.com/@types/unist/-/unist-3.0.2.tgz", | |||
| @@ -10715,6 +10734,12 @@ | |||
| "integrity": "sha512-l32Xp/TLgWb8ReqbVJAFIvXmY7go4nTxxlWiAFyhoQw9RKEOHBZNnyGvJWqDVSPmq3Y9HlM4npqF/T6VMOXhww==", | |||
| "deprecated": "dommatrix is no longer maintained. Please use @thednp/dommatrix." | |||
| }, | |||
| "node_modules/dompurify": { | |||
| "version": "3.1.6", | |||
| "resolved": "https://registry.npmmirror.com/dompurify/-/dompurify-3.1.6.tgz", | |||
| "integrity": "sha512-cTOAhc36AalkjtBpfG6O8JimdTMWNXjiePT2xQH/ppBGi/4uIpmj8eKyIkMJErXWARyINV/sB38yf8JCLF5pbQ==", | |||
| "license": "(MPL-2.0 OR Apache-2.0)" | |||
| }, | |||
| "node_modules/domutils": { | |||
| "version": "2.8.0", | |||
| "resolved": "https://registry.npmmirror.com/domutils/-/domutils-2.8.0.tgz", | |||
| @@ -28,6 +28,7 @@ | |||
| "classnames": "^2.5.1", | |||
| "dagre": "^0.8.5", | |||
| "dayjs": "^1.11.10", | |||
| "dompurify": "^3.1.6", | |||
| "elkjs": "^0.9.3", | |||
| "eventsource-parser": "^1.1.2", | |||
| "human-id": "^4.1.1", | |||
| @@ -63,6 +64,7 @@ | |||
| "@testing-library/jest-dom": "^6.4.5", | |||
| "@testing-library/react": "^15.0.7", | |||
| "@types/dagre": "^0.7.52", | |||
| "@types/dompurify": "^3.0.5", | |||
| "@types/jest": "^29.5.12", | |||
| "@types/lodash": "^4.14.202", | |||
| "@types/react": "^18.0.33", | |||
| @@ -2,6 +2,7 @@ import Image from '@/components/image'; | |||
| import { IChunk } from '@/interfaces/database/knowledge'; | |||
| import { Card, Checkbox, CheckboxProps, Flex, Popover, Switch } from 'antd'; | |||
| import classNames from 'classnames'; | |||
| import DOMPurify from 'dompurify'; | |||
| import { useState } from 'react'; | |||
| import { ChunkTextMode } from '../../constant'; | |||
| @@ -73,7 +74,9 @@ const ChunkCard = ({ | |||
| className={styles.content} | |||
| > | |||
| <div | |||
| dangerouslySetInnerHTML={{ __html: item.content_with_weight }} | |||
| dangerouslySetInnerHTML={{ | |||
| __html: DOMPurify.sanitize(item.content_with_weight), | |||
| }} | |||
| className={classNames({ | |||
| [styles.contentEllipsis]: textMode === ChunkTextMode.Ellipse, | |||
| })} | |||
| @@ -2,6 +2,7 @@ import SvgIcon from '@/components/svg-icon'; | |||
| import { useTranslate } from '@/hooks/commonHooks'; | |||
| import { useSelectParserList } from '@/hooks/userSettingHook'; | |||
| import { Col, Divider, Empty, Row, Typography } from 'antd'; | |||
| import DOMPurify from 'dompurify'; | |||
| import { useMemo } from 'react'; | |||
| import styles from './index.less'; | |||
| import { ImageMap } from './utils'; | |||
| @@ -39,7 +40,7 @@ const CategoryPanel = ({ chunkMethod }: { chunkMethod: string }) => { | |||
| </Title> | |||
| <p | |||
| dangerouslySetInnerHTML={{ | |||
| __html: item.description, | |||
| __html: DOMPurify.sanitize(item.description), | |||
| }} | |||
| ></p> | |||
| <Title level={5}> | |||
| @@ -6,6 +6,7 @@ import { IChunk } from '@/interfaces/database/knowledge'; | |||
| import { getExtension } from '@/utils/documentUtils'; | |||
| import { InfoCircleOutlined } from '@ant-design/icons'; | |||
| import { Button, Flex, Popover, Space } from 'antd'; | |||
| import DOMPurify from 'dompurify'; | |||
| import { useCallback } from 'react'; | |||
| import Markdown from 'react-markdown'; | |||
| import reactStringReplace from 'react-string-replace'; | |||
| @@ -94,7 +95,7 @@ const MarkdownContent = ({ | |||
| <Space direction={'vertical'}> | |||
| <div | |||
| dangerouslySetInnerHTML={{ | |||
| __html: chunkItem?.content_with_weight, | |||
| __html: DOMPurify.sanitize(chunkItem?.content_with_weight), | |||
| }} | |||
| className={styles.chunkContentText} | |||
| ></div> | |||