Pentest 58.1 MEDIUM: documentType jetzt mit echter Whitelist-Validierung
Bisher lief documentType nur durch stripHtml – ein beliebiger String
("NICHT_ERLAUBT", "DROP TABLE ...", Tippfehler) wurde 1:1 als
ContractDocument.documentType in die DB geschrieben. Das brach
Frontend-Filter, Lieferbestätigung-Auto-Activation und Reports.
Neuer validateContractDocumentType-Helper in utils/sanitize:
- Whitelist ALLOWED_CONTRACT_DOCUMENT_TYPES (8 Werte, gespiegelt aus
Frontend CONTRACT_DOCUMENT_TYPES)
- Case-insensitiver Match, Rückgabe ist immer der kanonische Wert
- Wirft sprechende 400-Fehlermeldung mit Liste der erlaubten Werte
Eingesetzt in allen 3 Schreibpfaden:
- contract.controller.uploadContractDocument (multer-Datei wird bei
Reject sauber gelöscht)
- cachedEmail.controller.saveEmailAsContractDocument
- cachedEmail.controller.saveAttachmentAsContractDocument
Audit-Log + maybeActivateOnDeliveryConfirmation nutzen jetzt den
kanonischen Wert (statt der rohen Eingabe), damit Reports
einheitlich aussehen.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -178,6 +178,53 @@ export function sanitizeCustomers<T extends Record<string, unknown>>(customers:
|
||||
// - Tags + gefährliche Schemata via stripHtml
|
||||
// - CRLF auf Newline normalisieren, keine Carriage-Returns persistieren
|
||||
// - Length-Cap default 2000 Zeichen (genug für sinnvolle Anmerkungen)
|
||||
// Pentest 58.1 (MEDIUM, 2026-06-01): documentType wurde nur durch
|
||||
// stripHtml geschickt, aber NICHT gegen eine Whitelist geprüft. Damit
|
||||
// landeten beliebige Strings (`NICHT_ERLAUBT`, `DROP TABLE …`,
|
||||
// Tippfehler-Werte aus alten UI-Versionen) als documentType in der
|
||||
// ContractDocument-Tabelle und brachen Frontend-Filter, Auto-Activation
|
||||
// (Lieferbestätigung-Trigger) und Reports.
|
||||
//
|
||||
// Whitelist spiegelt die Konstante CONTRACT_DOCUMENT_TYPES aus
|
||||
// SaveAttachmentModal / SaveEmailAsPdfModal im Frontend. Beide
|
||||
// Listen MÜSSEN synchron gehalten werden – idealerweise später
|
||||
// in eine geteilte Konfiguration gehoben.
|
||||
export const ALLOWED_CONTRACT_DOCUMENT_TYPES = [
|
||||
'Auftragsformular',
|
||||
'Auftragsbestätigung',
|
||||
'Lieferbestätigung',
|
||||
'Vertragsunterlagen',
|
||||
'Vollmacht',
|
||||
'Widerrufsbelehrung',
|
||||
'Preisblatt',
|
||||
'Sonstiges',
|
||||
] as const;
|
||||
|
||||
const CONTRACT_DOCUMENT_TYPE_SET: Set<string> = new Set(ALLOWED_CONTRACT_DOCUMENT_TYPES);
|
||||
|
||||
/**
|
||||
* Validiert + normalisiert einen documentType-Wert. Wirft einen Fehler
|
||||
* mit klarer Liste, wenn der Wert nicht in der Whitelist steht (der
|
||||
* aufrufende Controller mappt das auf 400). Trimmt Whitespace und macht
|
||||
* den Vergleich case-insensitive – damit `"lieferbestätigung"` aus
|
||||
* Drittsystemen sauber matched, aber `"Lieferbestätigung_DROP"` rausfliegt.
|
||||
*/
|
||||
export function validateContractDocumentType(raw: unknown): string {
|
||||
if (typeof raw !== 'string') {
|
||||
throw new Error(`documentType ist erforderlich. Erlaubt: ${ALLOWED_CONTRACT_DOCUMENT_TYPES.join(', ')}`);
|
||||
}
|
||||
const cleaned = stripHtml(raw) as string;
|
||||
const trimmed = cleaned.trim();
|
||||
if (trimmed === '') {
|
||||
throw new Error(`documentType ist erforderlich. Erlaubt: ${ALLOWED_CONTRACT_DOCUMENT_TYPES.join(', ')}`);
|
||||
}
|
||||
const canonical = ALLOWED_CONTRACT_DOCUMENT_TYPES.find((t) => t.toLowerCase() === trimmed.toLowerCase());
|
||||
if (!canonical) {
|
||||
throw new Error(`Ungültiger documentType '${trimmed}'. Erlaubt: ${ALLOWED_CONTRACT_DOCUMENT_TYPES.join(', ')}`);
|
||||
}
|
||||
return canonical;
|
||||
}
|
||||
|
||||
const NOTES_DEFAULT_MAX = 2000;
|
||||
export function sanitizeNotes(raw: unknown, maxLength: number = NOTES_DEFAULT_MAX): string | null {
|
||||
if (raw == null) return null;
|
||||
|
||||
Reference in New Issue
Block a user