dservian commited on
Commit
ad0d761
·
1 Parent(s): a9048a7

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={{ __html: item.content_with_weight }}
 
 
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>