From 101369c2058ab477dd59a9653f469ae37a66be13 Mon Sep 17 00:00:00 2001 From: duffyduck Date: Wed, 3 Jun 2026 18:06:19 +0200 Subject: [PATCH] =?UTF-8?q?EmailDetail:=20Links=20immer=20im=20neuen=20Tab?= =?UTF-8?q?=20=C3=B6ffnen?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Nach DOMPurify-Sanitize alle -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. --- docs/todo.md | 6 ++++ frontend/src/components/email/EmailDetail.tsx | 31 ++++++++++++------- 2 files changed, 26 insertions(+), 11 deletions(-) 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 ? (
) : (