Pentest R86: Vertrags-Identifier max 100 + Charset-Whitelist

R86.1 LOW + R86.2 LOW: >999-Zeichen liefen in DB-Overflow (500
statt 400), Attribut-Injection (`foo" onerror=…` ohne
umschließenden Tag) überlebte stripHtml.

Fix: validateContractIdentifier() (max 100,
^[A-Za-z0-9_\-/. ]{0,100}$) in sanitize.ts, eingehängt in
sanitizeContractBody. Wirft ApiError(400, …). Literales Space
statt \s → kein CRLF/Tab → kein Header-Injection-Vektor in
CSV-/Mail-/PDF-Export. Greift auf alle fünf Identifier-Felder
(Provider + Sales-Platform). ContractForm-Inputs bekommen
maxLength={100} als UX-Schicht.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
2026-06-19 14:14:00 +02:00
parent 0b7bb89ebc
commit c8b86ca9a7
5 changed files with 115 additions and 7 deletions
+15
View File
@@ -97,6 +97,21 @@ isolierte Instanz (keine Multi-Tenancy im Code), Provisioning + Abrechnung
## ✅ Erledigt
- [x] **🔒 Pentest R86 Vertrags-Identifier härten**
- R86.1 (LOW): >999-Zeichen-Strings auf Kunden-/Vertrags-/
Auftragsnummer warfen 500 (DB-Overflow `VARCHAR(191)`) statt 400.
- R86.2 (LOW/INFO): Attribut-Injection ohne umschließenden Tag
(`foo" onerror=…`) überlebte `stripHtml` kein Risiko in der React-
UI, aber relevant für PDF/Mail/CSV-Export.
- Fix: zentraler `validateContractIdentifier()` in `sanitize.ts`
mit Max-100 und Whitelist `^[A-Za-z0-9_\-/. ]{0,100}$`. Bewusst
literales Space statt `\s`, damit kein CRLF/Tab passiert (Header-
Injection). Wirft `ApiError(400, …)` mit klarer Meldung.
- Eingehängt in `sanitizeContractBody` → läuft automatisch für alle
fünf Identifier-Felder bei Create/Update. ContractForm bekommt
`maxLength={100}` als UX-Schicht. Doku in
`docs/SECURITY-HARDENING.md` § Runde 86.
- [x] **🆕 Vertrag: Auftragsnummer bei Vertriebsplattform**
- Neues optionales Feld `Contract.orderNumberAtSalesPlatform`
(`VARCHAR(191) NULL`), Migration