diff --git a/docs/todo.md b/docs/todo.md
index 2f5f15b8..93aa1a0c 100644
--- a/docs/todo.md
+++ b/docs/todo.md
@@ -97,6 +97,12 @@ isolierte Instanz (keine Multi-Tenancy im Code), Provisioning + Abrechnung
## ✅ Erledigt
+- [x] **🆕 Email-Links öffnen im neuen Tab**
+ - In `EmailDetail` nach der DOMPurify-Sanitize jedes ``-Element
+ auf `target="_blank"` + `rel="noopener noreferrer"` gesetzt. Letzteres
+ verhindert window.opener-Tab-Hijacking. Sanitize + DOM-Walk laufen
+ in einem `useMemo`, das nur bei Wechsel der Email neu rechnet.
+
- [x] **🐞 assertSafePdf: jspdf-PDFs mit JPEGs fälschlich als „JavaScript" blockiert**
- Stage-Bug: User lädt Ausweis als „JPGs → PDF" hoch → 415 mit
Meldung „PDF enthält JavaScript-Action". Backend hat den jspdf-
diff --git a/frontend/src/components/email/EmailDetail.tsx b/frontend/src/components/email/EmailDetail.tsx
index 07c3589e..302837dd 100644
--- a/frontend/src/components/email/EmailDetail.tsx
+++ b/frontend/src/components/email/EmailDetail.tsx
@@ -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 -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 ? (
) : (