EmailDetail: Links immer im neuen Tab öffnen

Nach DOMPurify-Sanitize alle <a>-Elemente auf target="_blank" +
rel="noopener noreferrer" setzen. Letzteres verhindert
window.opener-Tab-Hijacking. Sanitize + DOM-Walk in useMemo, läuft
nur bei Wechsel der Email neu.
This commit is contained in:
2026-06-03 18:06:19 +02:00
parent e792fe4185
commit 101369c205
2 changed files with 26 additions and 11 deletions
+20 -11
View File
@@ -1,4 +1,4 @@
import { useState, useEffect } from 'react';
import { useState, useEffect, useMemo } from 'react';
import { Reply, Forward, RotateCcw, Star, Paperclip, Link2, X, Download, ExternalLink, Trash2, Undo2, Save, FileDown } from 'lucide-react';
import DOMPurify from 'dompurify';
import { CachedEmail, cachedEmailApi } from '../../services/api';
@@ -52,6 +52,24 @@ export default function EmailDetail({
setLocalStarred(email.isStarred);
}, [email.id, email.isStarred]);
// Email-Body sanitizen + alle <a>-Links auf neuen Tab umstellen.
// rel="noopener noreferrer" verhindert window.opener-Tab-Hijacking.
const safeHtmlBody = useMemo(() => {
if (!email.htmlBody) return '';
const sanitized = DOMPurify.sanitize(email.htmlBody, {
FORBID_TAGS: ['script', 'style', 'iframe', 'object', 'embed', 'form'],
FORBID_ATTR: ['onerror', 'onload', 'onclick', 'onmouseover', 'onfocus'],
ADD_ATTR: ['target'],
});
const wrapper = document.createElement('div');
wrapper.innerHTML = sanitized;
wrapper.querySelectorAll('a').forEach((a) => {
a.setAttribute('target', '_blank');
a.setAttribute('rel', 'noopener noreferrer');
});
return wrapper.innerHTML;
}, [email.htmlBody]);
const toggleStarMutation = useMutation({
mutationFn: () => cachedEmailApi.toggleStar(email.id),
onMutate: () => {
@@ -411,16 +429,7 @@ export default function EmailDetail({
{showHtml && email.htmlBody ? (
<div
className="prose prose-sm max-w-none"
dangerouslySetInnerHTML={{
__html: DOMPurify.sanitize(email.htmlBody, {
// Scripte, Inline-Handler, Form-Elemente, externe Referenzen verbieten.
// Bilder + Links mit target=_blank bleiben zugelassen.
FORBID_TAGS: ['script', 'style', 'iframe', 'object', 'embed', 'form'],
FORBID_ATTR: ['onerror', 'onload', 'onclick', 'onmouseover', 'onfocus'],
// Links in neuen Tabs öffnen (verhindert window.opener-Angriffe)
ADD_ATTR: ['target'],
}),
}}
dangerouslySetInnerHTML={{ __html: safeHtmlBody }}
/>
) : (
<pre className="whitespace-pre-wrap text-sm text-gray-700 font-sans">