Pentest 62.7 LOW: deliveryDate / confirmationDate ISO-8601-Validierung

Bisher gingen XSS-Payloads in deliveryDate (saveEmailAsContractDocument,
saveAttachmentAsContractDocument, uploadContractDocument) und
confirmationDate (Cancellation-Confirmation-Upload) mit 200 durch.
Das Datum wurde silent als null behandelt; Impact gering, aber
schlechte API-Hygiene.

Neuer validateOptionalIsoDate-Helper in utils/sanitize:
- ISO-8601-Regex YYYY-MM-DD oder YYYY-MM-DDTHH:MM:SS(.fff)?(Z|+HH:MM)?
- null / leerer String / undefined sind OK (Optional-Semantik)
- Sonstige Eingaben werfen 400 mit klarer Meldung

Eingesetzt in:
- contract.controller uploadContractDocument (multer-Datei wird bei
  Reject sauber gelöscht)
- cachedEmail.controller saveEmailAsContractDocument +
  saveAttachmentAsContractDocument: Validierung früh, BEVOR Dateien
  geschrieben werden – kein Datei-Müll bei Reject
- upload.routes handleContractDocumentUpload (cancellationConfirmation*)

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-06-02 08:27:22 +02:00
parent 5fa9d4d4f3
commit 518139438e
4 changed files with 73 additions and 10 deletions
@@ -8,7 +8,7 @@ import { sendEmail, SmtpCredentials, SendEmailParams, EmailAttachment } from '..
import { fetchAttachment, appendToSent, ImapCredentials } from '../services/imapService.js';
import { getImapSmtpSettings } from '../services/emailProvider/emailProviderService.js';
import { decrypt } from '../utils/encryption.js';
import { sanitizeNotes, stripHtml, validateContractDocumentType } from '../utils/sanitize.js';
import { sanitizeNotes, stripHtml, validateContractDocumentType, validateOptionalIsoDate } from '../utils/sanitize.js';
import { logChange } from '../services/audit.service.js';
import { ApiResponse, AuthRequest } from '../types/index.js';
import { getCustomerTargets, getContractTargets, getIdentityDocumentTargets, getBankCardTargets, documentTargets } from '../config/documentTargets.config.js';
@@ -1836,6 +1836,15 @@ export async function saveEmailAsContractDocument(req: AuthRequest, res: Respons
return;
}
// Pentest 62.7: deliveryDate früh validieren, bevor wir Dateien schreiben.
let deliveryDate: string | null;
try {
deliveryDate = validateOptionalIsoDate(req.body?.deliveryDate, 'deliveryDate');
} catch (err) {
res.status(400).json({ success: false, error: err instanceof Error ? err.message : 'Ungültiges Lieferdatum' } as ApiResponse);
return;
}
const email = await cachedEmailService.getCachedEmailById(emailId);
if (!email) {
res.status(404).json({ success: false, error: 'E-Mail nicht gefunden' } as ApiResponse);
@@ -1898,8 +1907,8 @@ export async function saveEmailAsContractDocument(req: AuthRequest, res: Respons
});
});
// Falls Lieferbestätigung: DRAFT → ACTIVE + startDate setzen falls leer
const deliveryDate = typeof req.body?.deliveryDate === 'string' ? req.body.deliveryDate : null;
// Falls Lieferbestätigung: DRAFT → ACTIVE + startDate setzen falls leer.
// deliveryDate wurde oben schon validiert (Pentest 62.7).
await maybeActivateOnDeliveryConfirmation(contract.id, validatedType, req, deliveryDate);
res.json({ success: true, data: doc } as ApiResponse);
@@ -2096,6 +2105,15 @@ export async function saveAttachmentAsContractDocument(req: AuthRequest, res: Re
return;
}
// Pentest 62.7: deliveryDate früh validieren, bevor wir Dateien schreiben.
let deliveryDate: string | null;
try {
deliveryDate = validateOptionalIsoDate(req.body?.deliveryDate, 'deliveryDate');
} catch (err) {
res.status(400).json({ success: false, error: err instanceof Error ? err.message : 'Ungültiges Lieferdatum' } as ApiResponse);
return;
}
const email = await cachedEmailService.getCachedEmailById(emailId);
if (!email) {
res.status(404).json({ success: false, error: 'E-Mail nicht gefunden' } as ApiResponse);
@@ -2201,8 +2219,8 @@ export async function saveAttachmentAsContractDocument(req: AuthRequest, res: Re
});
});
// Falls Lieferbestätigung: DRAFT → ACTIVE + startDate setzen falls leer
const deliveryDate = typeof req.body?.deliveryDate === 'string' ? req.body.deliveryDate : null;
// Falls Lieferbestätigung: DRAFT → ACTIVE + startDate setzen falls leer.
// deliveryDate wurde oben schon validiert (Pentest 62.7).
await maybeActivateOnDeliveryConfirmation(contract.id, validatedType, req, deliveryDate);
res.json({ success: true, data: doc } as ApiResponse);