mirror of
https://github.com/dreamhunter2333/cloudflare_temp_email.git
synced 2026-08-06 05:53:36 +08:00
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 <tuanaiseo@gmail.com>
This commit is contained in:
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
25
frontend/src/utils/__tests__/sanitize-html.test.js
Normal file
25
frontend/src/utils/__tests__/sanitize-html.test.js
Normal file
@@ -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('<strong>Notice</strong>')).toBe('<strong>Notice</strong>');
|
||||
});
|
||||
|
||||
it('removes executable markup and unsafe attributes', () => {
|
||||
const sanitized = sanitizeHtml(
|
||||
'<script>alert(1)</script><img src="x" onerror="alert(1)">'
|
||||
);
|
||||
|
||||
expect(sanitized).not.toContain('<script');
|
||||
expect(sanitized).not.toContain('onerror');
|
||||
expect(sanitized).toContain('<img src="x">');
|
||||
});
|
||||
|
||||
it('returns an empty string for non-string values', () => {
|
||||
expect(sanitizeHtml(null)).toBe('');
|
||||
expect(sanitizeHtml({ value: '<strong>unsafe ref</strong>' })).toBe('');
|
||||
});
|
||||
});
|
||||
5
frontend/src/utils/sanitize-html.js
Normal file
5
frontend/src/utils/sanitize-html.js
Normal file
@@ -0,0 +1,5 @@
|
||||
import DOMPurify from 'dompurify';
|
||||
|
||||
export const sanitizeHtml = (html) => {
|
||||
return DOMPurify.sanitize(typeof html === 'string' ? html : '');
|
||||
};
|
||||
@@ -1,13 +1,16 @@
|
||||
<script setup>
|
||||
import { computed } from 'vue'
|
||||
import { GithubAlt, Discord, Telegram } from '@vicons/fa'
|
||||
import { useGlobalState } from '../../store'
|
||||
import { sanitizeHtml } from '../../utils/sanitize-html'
|
||||
const { announcement } = useGlobalState()
|
||||
const safeAnnouncement = computed(() => sanitizeHtml(announcement.value))
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="center">
|
||||
<n-card :bordered="false" embedded>
|
||||
<div v-html="announcement"></div>
|
||||
<div v-html="safeAnnouncement"></div>
|
||||
<n-button tag="a" target="_blank" href="https://github.com/dreamhunter2333/cloudflare_temp_email">
|
||||
<template #icon>
|
||||
<n-icon :component="GithubAlt" />
|
||||
|
||||
Reference in New Issue
Block a user