fix(web): ArticlePhonePreview XSS 修复 — DOMPurify 净化 dangerouslySetInnerHTML

- 安装 dompurify + @types/dompurify
- ArticlePhonePreview 使用 DOMPurify.sanitize() 防止 HTML 注入
This commit is contained in:
iven
2026-05-21 22:34:58 +08:00
parent fd994edf3e
commit 21481dbd88
3 changed files with 31 additions and 1 deletions

View File

@@ -24,6 +24,7 @@
"antd": "^6.3.5",
"axios": "^1.15.0",
"dayjs": "^1.11.20",
"dompurify": "^3.4.5",
"react": "^19.2.4",
"react-dom": "^19.2.4",
"react-router-dom": "^7.14.0",
@@ -36,6 +37,7 @@
"@tailwindcss/vite": "^4.2.2",
"@testing-library/jest-dom": "^6.9.1",
"@testing-library/react": "^16.3.2",
"@types/dompurify": "^3.2.0",
"@types/node": "^24.12.2",
"@types/react": "^19.2.14",
"@types/react-dom": "^19.2.3",

View File

@@ -38,6 +38,9 @@ importers:
dayjs:
specifier: ^1.11.20
version: 1.11.20
dompurify:
specifier: ^3.4.5
version: 3.4.5
react:
specifier: ^19.2.4
version: 19.2.5
@@ -69,6 +72,9 @@ importers:
'@testing-library/react':
specifier: ^16.3.2
version: 16.3.2(@testing-library/dom@10.4.1)(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.5(react@19.2.5))(react@19.2.5)
'@types/dompurify':
specifier: ^3.2.0
version: 3.2.0
'@types/node':
specifier: ^24.12.2
version: 24.12.2
@@ -1172,6 +1178,10 @@ packages:
'@types/deep-eql@4.0.2':
resolution: {integrity: sha512-c9h9dVVMigMPc4bwTvC5dxqtqJZwQPePsWjPlpSOnojbor6pGqdk541lfA7AqFQr5pB1BRdq0juY9db81BwyFw==}
'@types/dompurify@3.2.0':
resolution: {integrity: sha512-Fgg31wv9QbLDA0SpTOXO3MaxySc4DKGLi8sna4/Utjo4r3ZRPdCt4UQee8BWr+Q5z21yifghREPJGYaEOEIACg==}
deprecated: This is a stub types definition. dompurify provides its own type definitions, so you do not need this installed.
'@types/estree@1.0.8':
resolution: {integrity: sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==}
@@ -1201,6 +1211,9 @@ packages:
'@types/statuses@2.0.6':
resolution: {integrity: sha512-xMAgYwceFhRA2zY+XbEA7mxYbA093wdiW8Vu6gZPGWy9cmOyU9XesH1tNcEWsKFd5Vzrqx5T3D38PWx1FIIXkA==}
'@types/trusted-types@2.0.7':
resolution: {integrity: sha512-ScaPdn1dQczgbl0QFTeTOmVHFULt394XJgOQNoyVhZ6r2vLnMLJfBPd53SB52T/3G36VI1/g2MZaX0cwDuXsfw==}
'@typescript-eslint/eslint-plugin@8.58.1':
resolution: {integrity: sha512-eSkwoemjo76bdXl2MYqtxg51HNwUSkWfODUOQ3PaTLZGh9uIWWFZIjyjaJnex7wXDu+TRx+ATsnSxdN9YWfRTQ==}
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
@@ -1769,6 +1782,9 @@ packages:
dom7@3.0.0:
resolution: {integrity: sha512-oNlcUdHsC4zb7Msx7JN3K0Nro1dzJ48knvBOnDPKJ2GV9wl1i5vydJZUSyOfrkKFDZEud/jBsTk92S/VGSAe/g==}
dompurify@3.4.5:
resolution: {integrity: sha512-OrwIBKsdNSVEeubdJ1HBv/wNENRM9ytAVCv7YXt//A3vPdVMNuACRqK9mXCGCBW2ln7BT/A4X0jXHo2Gu89miA==}
dunder-proto@1.0.1:
resolution: {integrity: sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==}
engines: {node: '>= 0.4'}
@@ -4199,6 +4215,10 @@ snapshots:
'@types/deep-eql@4.0.2': {}
'@types/dompurify@3.2.0':
dependencies:
dompurify: 3.4.5
'@types/estree@1.0.8': {}
'@types/event-emitter@0.3.5': {}
@@ -4225,6 +4245,9 @@ snapshots:
'@types/statuses@2.0.6': {}
'@types/trusted-types@2.0.7':
optional: true
'@typescript-eslint/eslint-plugin@8.58.1(@typescript-eslint/parser@8.58.1(eslint@9.39.4(jiti@2.6.1))(typescript@6.0.2))(eslint@9.39.4(jiti@2.6.1))(typescript@6.0.2)':
dependencies:
'@eslint-community/regexpp': 4.12.2
@@ -4906,6 +4929,10 @@ snapshots:
dependencies:
ssr-window: 3.0.0
dompurify@3.4.5:
optionalDependencies:
'@types/trusted-types': 2.0.7
dunder-proto@1.0.1:
dependencies:
call-bind-apply-helpers: 1.0.2

View File

@@ -1,4 +1,5 @@
import { useMemo } from 'react';
import DOMPurify from 'dompurify';
interface ArticlePhonePreviewProps {
title: string;
@@ -240,7 +241,7 @@ export default function ArticlePhonePreview({
<div className="mp-content">
{content && content !== '<p><br></p>' ? (
<div dangerouslySetInnerHTML={{ __html: content }} />
<div dangerouslySetInnerHTML={{ __html: DOMPurify.sanitize(content) }} />
) : (
<div className="mp-empty">