diff --git a/CHANGELOG.md b/CHANGELOG.md
index 3d23437c..438e48d8 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -15,6 +15,7 @@
### Bug Fixes
+- fix: |Frontend| 使用共享 DOMPurify 净化逻辑处理关于页面与启动通知中的 HTML 公告,避免 `ANNOUNCEMENT` 中的可执行标签或事件属性造成 XSS
- fix: |Worker| 按邮件认证规范修复垃圾邮件检测:SPF、DKIM、DMARC 的 `none` 及 SPF/DKIM `neutral` 按认证方法不存在处理,并忽略未注册结果和不支持的方法版本;`JUNK_MAIL_FORCE_PASS_LIST` 仍要求明确返回受支持的 `pass`
- fix: |Admin| 管理后台删除邮箱地址时,先删除该地址的邮件、发件记录、自动回复等关联数据,最后再删除地址本身;此前地址行先被删除导致按地址名匹配的子查询查不到数据,邮件等记录被遗留在数据库中
- fix: |AI 提取| 强化提示词,要求 AI 保持邮件原始链接域名,避免小模型改写验证链接域名导致错误跳转(issue #1072)
diff --git a/CHANGELOG_EN.md b/CHANGELOG_EN.md
index d83c8081..505873f0 100644
--- a/CHANGELOG_EN.md
+++ b/CHANGELOG_EN.md
@@ -15,6 +15,7 @@
### Bug Fixes
+- fix: |Frontend| Sanitize HTML announcements in both the About page and startup notification through a shared DOMPurify helper, preventing executable tags or event attributes in `ANNOUNCEMENT` from causing XSS
- fix: |Worker| Align junk-mail checking with authentication standards: treat SPF, DKIM, and DMARC `none` plus SPF/DKIM `neutral` as absent, and ignore unregistered results and unsupported method versions; `JUNK_MAIL_FORCE_PASS_LIST` still requires an explicit supported `pass`
- fix: |Admin| When deleting an address from the admin panel, delete its mails, sender records, sendbox and auto-reply entries before removing the address row itself; previously the address row was deleted first, so the name-based subqueries matched nothing and the mails were left orphaned in the database
- fix: |AI Extract| Strengthen the prompt to keep original link domains from the email, preventing small models from rewriting verification-link domains (issue #1072)
diff --git a/frontend/src/api/index.js b/frontend/src/api/index.js
index 7362e665..ee826862 100644
--- a/frontend/src/api/index.js
+++ b/frontend/src/api/index.js
@@ -5,6 +5,7 @@ import axios from 'axios'
import i18n from '../i18n'
import { getFingerprint } from '../utils/fingerprint'
import { safeBearerHeader, safeHeaderValue } from '../utils/headers'
+import { sanitizeHtml } from '../utils/sanitize-html'
const API_BASE = import.meta.env.VITE_API_BASE || "";
const {
@@ -123,7 +124,7 @@ const getOpenSettings = async (message, notification) => {
notification.info({
content: () => {
return h("div", {
- innerHTML: announcement.value
+ innerHTML: sanitizeHtml(announcement.value)
});
}
});
diff --git a/frontend/src/utils/__tests__/sanitize-html.test.js b/frontend/src/utils/__tests__/sanitize-html.test.js
new file mode 100644
index 00000000..dff56ad5
--- /dev/null
+++ b/frontend/src/utils/__tests__/sanitize-html.test.js
@@ -0,0 +1,25 @@
+// @vitest-environment jsdom
+
+import { describe, expect, it } from 'vitest';
+import { sanitizeHtml } from '../sanitize-html';
+
+describe('sanitizeHtml', () => {
+ it('preserves safe announcement markup', () => {
+ expect(sanitizeHtml('Notice')).toBe('Notice');
+ });
+
+ it('removes executable markup and unsafe attributes', () => {
+ const sanitized = sanitizeHtml(
+ '
'
+ );
+
+ expect(sanitized).not.toContain('