From 8c883b269f3acd7d12ce009c9607bd481110717e Mon Sep 17 00:00:00 2001 From: tuanaiseo Date: Tue, 28 Jul 2026 16:49:32 +0700 Subject: [PATCH] fix(frontend): sanitize announcement HTML (#1039) Sanitize HTML announcements in both the About page and startup notification through a shared DOMPurify helper. Add regression tests and bilingual changelog entries. Co-authored-by: tuanaiseo --- CHANGELOG.md | 1 + CHANGELOG_EN.md | 1 + frontend/src/api/index.js | 3 ++- .../src/utils/__tests__/sanitize-html.test.js | 25 +++++++++++++++++++ frontend/src/utils/sanitize-html.js | 5 ++++ frontend/src/views/common/About.vue | 5 +++- 6 files changed, 38 insertions(+), 2 deletions(-) create mode 100644 frontend/src/utils/__tests__/sanitize-html.test.js create mode 100644 frontend/src/utils/sanitize-html.js 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(''); + }); + + it('returns an empty string for non-string values', () => { + expect(sanitizeHtml(null)).toBe(''); + expect(sanitizeHtml({ value: 'unsafe ref' })).toBe(''); + }); +}); diff --git a/frontend/src/utils/sanitize-html.js b/frontend/src/utils/sanitize-html.js new file mode 100644 index 00000000..56f62e37 --- /dev/null +++ b/frontend/src/utils/sanitize-html.js @@ -0,0 +1,5 @@ +import DOMPurify from 'dompurify'; + +export const sanitizeHtml = (html) => { + return DOMPurify.sanitize(typeof html === 'string' ? html : ''); +}; diff --git a/frontend/src/views/common/About.vue b/frontend/src/views/common/About.vue index 003bf575..0e2591f3 100644 --- a/frontend/src/views/common/About.vue +++ b/frontend/src/views/common/About.vue @@ -1,13 +1,16 @@