diff --git a/backend/src/utils/sanitize.ts b/backend/src/utils/sanitize.ts index adaa6ff8..312da415 100644 --- a/backend/src/utils/sanitize.ts +++ b/backend/src/utils/sanitize.ts @@ -358,6 +358,14 @@ export function validateOptionalIsoDate(raw: unknown, fieldLabel: string): strin // Kunden-/Vertrags-Nummer und bleibt deutlich unter dem VARCHAR(191)- // Limit der DB-Spalte. const CONTRACT_IDENTIFIER_FIELDS: ReadonlySet = new Set([ + // Interne CRM-Vertragsnummer. Wird beim Create serverseitig generiert + // (`generateContractNumber`), aber der Update-Path filtert sie nicht + // aus – ein Admin (oder Angreifer mit users:update+contracts:update- + // JWT) könnte sie sonst überschreiben. Pentest 2026-07-04 INFO: + // `` → + `stripHtml` → leerer String → Vertragsnummer silent überschrieben. + - Fix: `contractNumber` zu `CONTRACT_IDENTIFIER_FIELDS` hinzugefügt – + bestehende R86-Whitelist (`^[A-Za-z0-9_\-/. ]{0,100}$`) greift + automatisch, R87-Fix (Raw-Input vor stripHtml) ist auch drin. + Legitime `GAS-MPVJ4P10FE8` bleibt OK, alle Angriffs-Payloads → 400. + - Doku in `SECURITY-HARDENING.md § Runde 102`. + - [x] **🔧 Pentest R101.1 – Inline-Preview-Pfad refaktoriert + Diagnose-Log** - Pentester R101.1 (INFO/funktional) berichtet: `?disposition=inline` bewirkt nichts, Browser zeigt Download-Dialog. Die Logik im