Fix: Implement DOMPurify to sanitize HTML content before rendering (#1498)
Browse files### 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 Fix
web/package-lock.json
CHANGED
|
@@ -17,6 +17,7 @@
|
|
| 17 |
"classnames": "^2.5.1",
|
| 18 |
"dagre": "^0.8.5",
|
| 19 |
"dayjs": "^1.11.10",
|
|
|
|
| 20 |
"elkjs": "^0.9.3",
|
| 21 |
"eventsource-parser": "^1.1.2",
|
| 22 |
"human-id": "^4.1.1",
|
|
@@ -52,6 +53,7 @@
|
|
| 52 |
"@testing-library/jest-dom": "^6.4.5",
|
| 53 |
"@testing-library/react": "^15.0.7",
|
| 54 |
"@types/dagre": "^0.7.52",
|
|
|
|
| 55 |
"@types/jest": "^29.5.12",
|
| 56 |
"@types/lodash": "^4.14.202",
|
| 57 |
"@types/react": "^18.0.33",
|
|
@@ -4856,6 +4858,16 @@
|
|
| 4856 |
"@types/ms": "*"
|
| 4857 |
}
|
| 4858 |
},
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 4859 |
"node_modules/@types/eslint": {
|
| 4860 |
"version": "8.56.1",
|
| 4861 |
"resolved": "https://registry.npmmirror.com/@types/eslint/-/eslint-8.56.1.tgz",
|
|
@@ -5206,6 +5218,13 @@
|
|
| 5206 |
"integrity": "sha512-/Ad8+nIOV7Rl++6f1BdKxFSMgmoqEoYbHRpPcx3JEfv8VRsQe9Z4mCXeJBzxs7mbHY/XOZZuXlRNfhpVPbs6ZA==",
|
| 5207 |
"dev": true
|
| 5208 |
},
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 5209 |
"node_modules/@types/unist": {
|
| 5210 |
"version": "3.0.2",
|
| 5211 |
"resolved": "https://registry.npmmirror.com/@types/unist/-/unist-3.0.2.tgz",
|
|
@@ -10715,6 +10734,12 @@
|
|
| 10715 |
"integrity": "sha512-l32Xp/TLgWb8ReqbVJAFIvXmY7go4nTxxlWiAFyhoQw9RKEOHBZNnyGvJWqDVSPmq3Y9HlM4npqF/T6VMOXhww==",
|
| 10716 |
"deprecated": "dommatrix is no longer maintained. Please use @thednp/dommatrix."
|
| 10717 |
},
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 10718 |
"node_modules/domutils": {
|
| 10719 |
"version": "2.8.0",
|
| 10720 |
"resolved": "https://registry.npmmirror.com/domutils/-/domutils-2.8.0.tgz",
|
|
|
|
| 17 |
"classnames": "^2.5.1",
|
| 18 |
"dagre": "^0.8.5",
|
| 19 |
"dayjs": "^1.11.10",
|
| 20 |
+
"dompurify": "^3.1.6",
|
| 21 |
"elkjs": "^0.9.3",
|
| 22 |
"eventsource-parser": "^1.1.2",
|
| 23 |
"human-id": "^4.1.1",
|
|
|
|
| 53 |
"@testing-library/jest-dom": "^6.4.5",
|
| 54 |
"@testing-library/react": "^15.0.7",
|
| 55 |
"@types/dagre": "^0.7.52",
|
| 56 |
+
"@types/dompurify": "^3.0.5",
|
| 57 |
"@types/jest": "^29.5.12",
|
| 58 |
"@types/lodash": "^4.14.202",
|
| 59 |
"@types/react": "^18.0.33",
|
|
|
|
| 4858 |
"@types/ms": "*"
|
| 4859 |
}
|
| 4860 |
},
|
| 4861 |
+
"node_modules/@types/dompurify": {
|
| 4862 |
+
"version": "3.0.5",
|
| 4863 |
+
"resolved": "https://registry.npmmirror.com/@types/dompurify/-/dompurify-3.0.5.tgz",
|
| 4864 |
+
"integrity": "sha512-1Wg0g3BtQF7sSb27fJQAKck1HECM6zV1EB66j8JH9i3LCjYabJa0FSdiSgsD5K/RbrsR0SiraKacLB+T8ZVYAg==",
|
| 4865 |
+
"dev": true,
|
| 4866 |
+
"license": "MIT",
|
| 4867 |
+
"dependencies": {
|
| 4868 |
+
"@types/trusted-types": "*"
|
| 4869 |
+
}
|
| 4870 |
+
},
|
| 4871 |
"node_modules/@types/eslint": {
|
| 4872 |
"version": "8.56.1",
|
| 4873 |
"resolved": "https://registry.npmmirror.com/@types/eslint/-/eslint-8.56.1.tgz",
|
|
|
|
| 5218 |
"integrity": "sha512-/Ad8+nIOV7Rl++6f1BdKxFSMgmoqEoYbHRpPcx3JEfv8VRsQe9Z4mCXeJBzxs7mbHY/XOZZuXlRNfhpVPbs6ZA==",
|
| 5219 |
"dev": true
|
| 5220 |
},
|
| 5221 |
+
"node_modules/@types/trusted-types": {
|
| 5222 |
+
"version": "2.0.7",
|
| 5223 |
+
"resolved": "https://registry.npmmirror.com/@types/trusted-types/-/trusted-types-2.0.7.tgz",
|
| 5224 |
+
"integrity": "sha512-ScaPdn1dQczgbl0QFTeTOmVHFULt394XJgOQNoyVhZ6r2vLnMLJfBPd53SB52T/3G36VI1/g2MZaX0cwDuXsfw==",
|
| 5225 |
+
"dev": true,
|
| 5226 |
+
"license": "MIT"
|
| 5227 |
+
},
|
| 5228 |
"node_modules/@types/unist": {
|
| 5229 |
"version": "3.0.2",
|
| 5230 |
"resolved": "https://registry.npmmirror.com/@types/unist/-/unist-3.0.2.tgz",
|
|
|
|
| 10734 |
"integrity": "sha512-l32Xp/TLgWb8ReqbVJAFIvXmY7go4nTxxlWiAFyhoQw9RKEOHBZNnyGvJWqDVSPmq3Y9HlM4npqF/T6VMOXhww==",
|
| 10735 |
"deprecated": "dommatrix is no longer maintained. Please use @thednp/dommatrix."
|
| 10736 |
},
|
| 10737 |
+
"node_modules/dompurify": {
|
| 10738 |
+
"version": "3.1.6",
|
| 10739 |
+
"resolved": "https://registry.npmmirror.com/dompurify/-/dompurify-3.1.6.tgz",
|
| 10740 |
+
"integrity": "sha512-cTOAhc36AalkjtBpfG6O8JimdTMWNXjiePT2xQH/ppBGi/4uIpmj8eKyIkMJErXWARyINV/sB38yf8JCLF5pbQ==",
|
| 10741 |
+
"license": "(MPL-2.0 OR Apache-2.0)"
|
| 10742 |
+
},
|
| 10743 |
"node_modules/domutils": {
|
| 10744 |
"version": "2.8.0",
|
| 10745 |
"resolved": "https://registry.npmmirror.com/domutils/-/domutils-2.8.0.tgz",
|
web/package.json
CHANGED
|
@@ -28,6 +28,7 @@
|
|
| 28 |
"classnames": "^2.5.1",
|
| 29 |
"dagre": "^0.8.5",
|
| 30 |
"dayjs": "^1.11.10",
|
|
|
|
| 31 |
"elkjs": "^0.9.3",
|
| 32 |
"eventsource-parser": "^1.1.2",
|
| 33 |
"human-id": "^4.1.1",
|
|
@@ -63,6 +64,7 @@
|
|
| 63 |
"@testing-library/jest-dom": "^6.4.5",
|
| 64 |
"@testing-library/react": "^15.0.7",
|
| 65 |
"@types/dagre": "^0.7.52",
|
|
|
|
| 66 |
"@types/jest": "^29.5.12",
|
| 67 |
"@types/lodash": "^4.14.202",
|
| 68 |
"@types/react": "^18.0.33",
|
|
|
|
| 28 |
"classnames": "^2.5.1",
|
| 29 |
"dagre": "^0.8.5",
|
| 30 |
"dayjs": "^1.11.10",
|
| 31 |
+
"dompurify": "^3.1.6",
|
| 32 |
"elkjs": "^0.9.3",
|
| 33 |
"eventsource-parser": "^1.1.2",
|
| 34 |
"human-id": "^4.1.1",
|
|
|
|
| 64 |
"@testing-library/jest-dom": "^6.4.5",
|
| 65 |
"@testing-library/react": "^15.0.7",
|
| 66 |
"@types/dagre": "^0.7.52",
|
| 67 |
+
"@types/dompurify": "^3.0.5",
|
| 68 |
"@types/jest": "^29.5.12",
|
| 69 |
"@types/lodash": "^4.14.202",
|
| 70 |
"@types/react": "^18.0.33",
|
web/src/pages/add-knowledge/components/knowledge-chunk/components/chunk-card/index.tsx
CHANGED
|
@@ -2,6 +2,7 @@ import Image from '@/components/image';
|
|
| 2 |
import { IChunk } from '@/interfaces/database/knowledge';
|
| 3 |
import { Card, Checkbox, CheckboxProps, Flex, Popover, Switch } from 'antd';
|
| 4 |
import classNames from 'classnames';
|
|
|
|
| 5 |
import { useState } from 'react';
|
| 6 |
|
| 7 |
import { ChunkTextMode } from '../../constant';
|
|
@@ -73,7 +74,9 @@ const ChunkCard = ({
|
|
| 73 |
className={styles.content}
|
| 74 |
>
|
| 75 |
<div
|
| 76 |
-
dangerouslySetInnerHTML={{
|
|
|
|
|
|
|
| 77 |
className={classNames({
|
| 78 |
[styles.contentEllipsis]: textMode === ChunkTextMode.Ellipse,
|
| 79 |
})}
|
|
|
|
| 2 |
import { IChunk } from '@/interfaces/database/knowledge';
|
| 3 |
import { Card, Checkbox, CheckboxProps, Flex, Popover, Switch } from 'antd';
|
| 4 |
import classNames from 'classnames';
|
| 5 |
+
import DOMPurify from 'dompurify';
|
| 6 |
import { useState } from 'react';
|
| 7 |
|
| 8 |
import { ChunkTextMode } from '../../constant';
|
|
|
|
| 74 |
className={styles.content}
|
| 75 |
>
|
| 76 |
<div
|
| 77 |
+
dangerouslySetInnerHTML={{
|
| 78 |
+
__html: DOMPurify.sanitize(item.content_with_weight),
|
| 79 |
+
}}
|
| 80 |
className={classNames({
|
| 81 |
[styles.contentEllipsis]: textMode === ChunkTextMode.Ellipse,
|
| 82 |
})}
|
web/src/pages/add-knowledge/components/knowledge-setting/category-panel.tsx
CHANGED
|
@@ -2,6 +2,7 @@ import SvgIcon from '@/components/svg-icon';
|
|
| 2 |
import { useTranslate } from '@/hooks/commonHooks';
|
| 3 |
import { useSelectParserList } from '@/hooks/userSettingHook';
|
| 4 |
import { Col, Divider, Empty, Row, Typography } from 'antd';
|
|
|
|
| 5 |
import { useMemo } from 'react';
|
| 6 |
import styles from './index.less';
|
| 7 |
import { ImageMap } from './utils';
|
|
@@ -39,7 +40,7 @@ const CategoryPanel = ({ chunkMethod }: { chunkMethod: string }) => {
|
|
| 39 |
</Title>
|
| 40 |
<p
|
| 41 |
dangerouslySetInnerHTML={{
|
| 42 |
-
__html: item.description,
|
| 43 |
}}
|
| 44 |
></p>
|
| 45 |
<Title level={5}>
|
|
|
|
| 2 |
import { useTranslate } from '@/hooks/commonHooks';
|
| 3 |
import { useSelectParserList } from '@/hooks/userSettingHook';
|
| 4 |
import { Col, Divider, Empty, Row, Typography } from 'antd';
|
| 5 |
+
import DOMPurify from 'dompurify';
|
| 6 |
import { useMemo } from 'react';
|
| 7 |
import styles from './index.less';
|
| 8 |
import { ImageMap } from './utils';
|
|
|
|
| 40 |
</Title>
|
| 41 |
<p
|
| 42 |
dangerouslySetInnerHTML={{
|
| 43 |
+
__html: DOMPurify.sanitize(item.description),
|
| 44 |
}}
|
| 45 |
></p>
|
| 46 |
<Title level={5}>
|
web/src/pages/chat/markdown-content/index.tsx
CHANGED
|
@@ -6,6 +6,7 @@ import { IChunk } from '@/interfaces/database/knowledge';
|
|
| 6 |
import { getExtension } from '@/utils/documentUtils';
|
| 7 |
import { InfoCircleOutlined } from '@ant-design/icons';
|
| 8 |
import { Button, Flex, Popover, Space } from 'antd';
|
|
|
|
| 9 |
import { useCallback } from 'react';
|
| 10 |
import Markdown from 'react-markdown';
|
| 11 |
import reactStringReplace from 'react-string-replace';
|
|
@@ -94,7 +95,7 @@ const MarkdownContent = ({
|
|
| 94 |
<Space direction={'vertical'}>
|
| 95 |
<div
|
| 96 |
dangerouslySetInnerHTML={{
|
| 97 |
-
__html: chunkItem?.content_with_weight,
|
| 98 |
}}
|
| 99 |
className={styles.chunkContentText}
|
| 100 |
></div>
|
|
|
|
| 6 |
import { getExtension } from '@/utils/documentUtils';
|
| 7 |
import { InfoCircleOutlined } from '@ant-design/icons';
|
| 8 |
import { Button, Flex, Popover, Space } from 'antd';
|
| 9 |
+
import DOMPurify from 'dompurify';
|
| 10 |
import { useCallback } from 'react';
|
| 11 |
import Markdown from 'react-markdown';
|
| 12 |
import reactStringReplace from 'react-string-replace';
|
|
|
|
| 95 |
<Space direction={'vertical'}>
|
| 96 |
<div
|
| 97 |
dangerouslySetInnerHTML={{
|
| 98 |
+
__html: DOMPurify.sanitize(chunkItem?.content_with_weight),
|
| 99 |
}}
|
| 100 |
className={styles.chunkContentText}
|
| 101 |
></div>
|