Spam-Tab: Anhänge aus dem echten Junk-Ordner holen (Pentest R124)

Beim Spam-Feature wurden moveEmailToTrash/restoreEmailFromTrash auf
den echten Junk-Pfad umgestellt, vier Attachment-Funktionen im
Controller aber nicht: downloadAttachment, saveAttachmentTo,
saveAttachmentAsInvoice, saveAttachmentAsContractDocument bestimmten
den IMAP-Ordner weiter hart als
email.folder === 'SENT' ? 'Sent' : 'INBOX'.

Für SPAM-Mails landete das fälschlich auf INBOX. Da IMAP-UIDs pro
Ordner vergeben sind: bestenfalls 404, schlimmstenfalls (UID-Kollision
INBOX vs. Junk) der FALSCHE Anhang aus INBOX – der dann z.B. als
Rechnung/Vertragsdokument abgelegt würde. Kein Cross-Customer-Leak
(gleicher Kunde/Postfach), aber Datenintegritätsproblem.

Fix: an allen vier Stellen dieselbe Junk-Pfad-Logik wie in
moveEmailToTrash (email.folder === 'SPAM' → findJunkFolderPath).
findJunkFolderPath war in dem Controller noch nicht importiert.

Vom Pentester (R124) gefunden – beim ursprünglichen Feature übersehen.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
2026-07-27 16:26:21 +02:00
co-authored by Claude Opus 4.7
parent 61c33a993b
commit 0a0cbe0e53
2 changed files with 68 additions and 5 deletions
@@ -5,7 +5,7 @@ import * as cachedEmailService from '../services/cachedEmail.service.js';
import * as stressfreiEmailService from '../services/stressfreiEmail.service.js'; import * as stressfreiEmailService from '../services/stressfreiEmail.service.js';
import * as invoiceService from '../services/invoice.service.js'; import * as invoiceService from '../services/invoice.service.js';
import { sendEmail, SmtpCredentials, SendEmailParams, EmailAttachment } from '../services/smtpService.js'; import { sendEmail, SmtpCredentials, SendEmailParams, EmailAttachment } from '../services/smtpService.js';
import { fetchAttachment, appendToSent, ImapCredentials, humanizeImapError } from '../services/imapService.js'; import { fetchAttachment, appendToSent, ImapCredentials, humanizeImapError, findJunkFolderPath } from '../services/imapService.js';
import { getImapSmtpSettings } from '../services/emailProvider/emailProviderService.js'; import { getImapSmtpSettings } from '../services/emailProvider/emailProviderService.js';
import { decrypt } from '../utils/encryption.js'; import { decrypt } from '../utils/encryption.js';
import { sanitizeNotes, stripHtml, validateContractDocumentType, validateOptionalIsoDate, assertSafePdf } from '../utils/sanitize.js'; import { sanitizeNotes, stripHtml, validateContractDocumentType, validateOptionalIsoDate, assertSafePdf } from '../utils/sanitize.js';
@@ -740,7 +740,19 @@ export async function downloadAttachment(req: AuthRequest, res: Response): Promi
}; };
// Ordner basierend auf E-Mail-Typ bestimmen (INBOX oder Sent) // Ordner basierend auf E-Mail-Typ bestimmen (INBOX oder Sent)
const imapFolder = email.folder === 'SENT' ? 'Sent' : 'INBOX'; // SPAM-Mails liegen im Junk-Ordner (Name variiert je Server) IMAP-UIDs
// sind pro Ordner vergeben, daher MUSS der echte Junk-Pfad ermittelt
// werden, sonst wird der Anhang im falschen Ordner gesucht (404 oder,
// bei UID-Kollision, der falsche Anhang aus INBOX). Gleiches Muster wie
// cachedEmail.service.ts::moveEmailToTrash.
let imapFolder: string;
if (email.folder === 'SENT') {
imapFolder = 'Sent';
} else if (email.folder === 'SPAM') {
imapFolder = (await findJunkFolderPath(credentials)) || 'Junk';
} else {
imapFolder = 'INBOX';
}
// Anhang per IMAP abrufen // Anhang per IMAP abrufen
const attachment = await fetchAttachment(credentials, email.uid, filename, imapFolder); const attachment = await fetchAttachment(credentials, email.uid, filename, imapFolder);
@@ -1405,7 +1417,19 @@ export async function saveAttachmentTo(req: AuthRequest, res: Response): Promise
}; };
// Ordner basierend auf E-Mail-Typ bestimmen (INBOX oder Sent) // Ordner basierend auf E-Mail-Typ bestimmen (INBOX oder Sent)
const imapFolder = email.folder === 'SENT' ? 'Sent' : 'INBOX'; // SPAM-Mails liegen im Junk-Ordner (Name variiert je Server) IMAP-UIDs
// sind pro Ordner vergeben, daher MUSS der echte Junk-Pfad ermittelt
// werden, sonst wird der Anhang im falschen Ordner gesucht (404 oder,
// bei UID-Kollision, der falsche Anhang aus INBOX). Gleiches Muster wie
// cachedEmail.service.ts::moveEmailToTrash.
let imapFolder: string;
if (email.folder === 'SENT') {
imapFolder = 'Sent';
} else if (email.folder === 'SPAM') {
imapFolder = (await findJunkFolderPath(credentials)) || 'Junk';
} else {
imapFolder = 'INBOX';
}
// Anhang per IMAP abrufen // Anhang per IMAP abrufen
const attachment = await fetchAttachment(credentials, email.uid, filename, imapFolder); const attachment = await fetchAttachment(credentials, email.uid, filename, imapFolder);
@@ -2211,7 +2235,19 @@ export async function saveAttachmentAsInvoice(req: AuthRequest, res: Response):
}; };
// IMAP-Ordner bestimmen // IMAP-Ordner bestimmen
const imapFolder = email.folder === 'SENT' ? 'Sent' : 'INBOX'; // SPAM-Mails liegen im Junk-Ordner (Name variiert je Server) IMAP-UIDs
// sind pro Ordner vergeben, daher MUSS der echte Junk-Pfad ermittelt
// werden, sonst wird der Anhang im falschen Ordner gesucht (404 oder,
// bei UID-Kollision, der falsche Anhang aus INBOX). Gleiches Muster wie
// cachedEmail.service.ts::moveEmailToTrash.
let imapFolder: string;
if (email.folder === 'SENT') {
imapFolder = 'Sent';
} else if (email.folder === 'SPAM') {
imapFolder = (await findJunkFolderPath(credentials)) || 'Junk';
} else {
imapFolder = 'INBOX';
}
// Anhang vom IMAP-Server laden // Anhang vom IMAP-Server laden
const attachment = await fetchAttachment(credentials, email.uid, filename, imapFolder); const attachment = await fetchAttachment(credentials, email.uid, filename, imapFolder);
@@ -2370,7 +2406,19 @@ export async function saveAttachmentAsContractDocument(req: AuthRequest, res: Re
allowSelfSignedCerts: settings.allowSelfSignedCerts, allowSelfSignedCerts: settings.allowSelfSignedCerts,
}; };
const imapFolder = email.folder === 'SENT' ? 'Sent' : 'INBOX'; // SPAM-Mails liegen im Junk-Ordner (Name variiert je Server) IMAP-UIDs
// sind pro Ordner vergeben, daher MUSS der echte Junk-Pfad ermittelt
// werden, sonst wird der Anhang im falschen Ordner gesucht (404 oder,
// bei UID-Kollision, der falsche Anhang aus INBOX). Gleiches Muster wie
// cachedEmail.service.ts::moveEmailToTrash.
let imapFolder: string;
if (email.folder === 'SENT') {
imapFolder = 'Sent';
} else if (email.folder === 'SPAM') {
imapFolder = (await findJunkFolderPath(credentials)) || 'Junk';
} else {
imapFolder = 'INBOX';
}
const attachment = await fetchAttachment(credentials, email.uid, filename, imapFolder); const attachment = await fetchAttachment(credentials, email.uid, filename, imapFolder);
if (!attachment) { if (!attachment) {
+15
View File
@@ -97,6 +97,21 @@ isolierte Instanz (keine Multi-Tenancy im Code), Provisioning + Abrechnung
## ✅ Erledigt ## ✅ Erledigt
- [x] **🐞 Spam-Tab: Anhänge aus Junk-Ordner (Pentest R124-Fund)**
- Beim Spam-Feature wurden `moveEmailToTrash`/`restoreEmailFromTrash`
auf den echten Junk-Pfad umgestellt, aber vier Attachment-Funktionen
im Controller (`downloadAttachment`, `saveAttachmentTo`,
`saveAttachmentAsInvoice`, `saveAttachmentAsContractDocument`)
bestimmten den IMAP-Ordner weiter hart als
`email.folder === 'SENT' ? 'Sent' : 'INBOX'`. Für SPAM-Mails landete
das fälschlich auf INBOX. Da IMAP-UIDs pro Ordner vergeben sind:
404 (Anhang nicht gefunden) oder bei UID-Kollision der falsche
Anhang aus INBOX (z.B. als Rechnung/Vertragsdokument abgelegt).
Kein Cross-Customer-Leak (bleibt im selben Postfach), aber echtes
Datenintegritätsproblem.
- Fix: an allen vier Stellen dieselbe Junk-Pfad-Logik wie in
`moveEmailToTrash` + `findJunkFolderPath` importiert.
- [x] **📧 E-Mail-Client: Spam-Ordner als eigener Tab** - [x] **📧 E-Mail-Client: Spam-Ordner als eigener Tab**
- Neuer Tab „Spam" (zwischen Gesendet und Papierkorb) im E-Mail-Client, - Neuer Tab „Spam" (zwischen Gesendet und Papierkorb) im E-Mail-Client,
zeigt den Junk-/Spam-Ordner des gewählten Postfachs damit zeigt den Junk-/Spam-Ordner des gewählten Postfachs damit