Pentest 81.1 (MEDIUM): Self-Forward erzeugte Mail-Loop am Provider

Bug: Die Stressfrei-Adresse selbst (max@stressfrei-wechseln.net)
konnte als zusätzliches Weiterleitungsziel eingetragen werden,
auch Plus-Varianten. Plesk leitet auf sich selbst um → Mail-Loop.

Backend setAdditionalForwards: lädt zusätzlich meta.email, vergleicht
canonicalEmailKey gegen canonicalEmailKey(meta.email). Bei Treffer
hartes ApiError(400) mit klarer "zeigt auf die Adresse selbst –
Mail-Loop"-Meldung statt silent dedup – der User soll merken, dass
sein Eintrag bewusst abgelehnt wurde.

Frontend AdditionalForwardsModal: zusätzliche proaktive Validierung
im Sub-Modal mit identischem canonicalize-Helper. Neuer selfEmail-
Prop, damit auch der Create-Modus (vor Persist) den Check fahren
kann. Spart Roundtrip + sofort sprechende Meldung.
This commit is contained in:
2026-06-18 15:55:01 +02:00
parent b3469483ca
commit 5bb048c534
3 changed files with 59 additions and 3 deletions
@@ -292,15 +292,21 @@ export async function deleteEmail(id: number) {
*
* Pentest 71.4: DB-Update wird bei Provider-Sync-Fehler zurückgerollt,
* damit Plesk und DB nicht auseinanderlaufen.
*
* Pentest 81.1: Self-Forward wird hart abgelehnt würde sonst am
* Provider einen Mail-Loop erzeugen (Stressfrei-Adresse leitet auf
* sich selbst um → unendliche Weiterleitung).
*/
export async function setAdditionalForwards(
id: number,
emails: string[],
): Promise<{ success: boolean; forwardTargets?: string[]; error?: string }> {
// Kunden-Stamm-Mail holen für Dedup gegen das (immer mit-gesetzte) Default-Ziel.
// Kunden-Stamm-Mail + eigene Email holen für Dedup gegen die fest
// gesetzten Ziele bzw. die Stressfrei-Adresse selbst.
const meta = await prisma.stressfreiEmail.findUnique({
where: { id },
select: {
email: true,
additionalForwardingEmails: true,
customer: { select: { email: true } },
},
@@ -312,6 +318,7 @@ export async function setAdditionalForwards(
const customerEmailKey = meta.customer?.email
? canonicalEmailKey(meta.customer.email)
: null;
const selfKey = canonicalEmailKey(meta.email);
// Input normalisieren + Duplikate raus.
const seen = new Set<string>();
@@ -320,6 +327,16 @@ export async function setAdditionalForwards(
for (const raw of emails) {
const ok = assertValidForwardingEmail(raw);
const key = canonicalEmailKey(ok);
// 81.1: Eintrag, der auf die Adresse selbst zeigt, würde einen
// Mail-Loop am Provider erzeugen. Hart ablehnen mit klarer
// Fehlermeldung, statt silent zu droppen der User soll merken,
// dass sein Eintrag bewusst nicht akzeptiert wurde.
if (key === selfKey) {
throw new ApiError(
400,
`"${ok}" zeigt auf die Adresse selbst Mail-Loop. Bitte eine andere Weiterleitungsadresse wählen.`,
);
}
if (!seen.has(key)) {
seen.add(key);
cleaned.push(ok);