Security-Hardening Runde 13: Live-Vollmacht-Konsistenz + embedded DTOs
Pentest Runde 10: MEDIUM – Stale Token nach Vollmacht-Widerruf: Selbst ein frischer Portal-Login lieferte JWT mit representedCustomer- Ids/representedCustomers, obwohl die Vollmacht widerrufen war. Live- Check beim Datenzugriff fing das ab (403), aber die UI zeigte weiter „kann vertreten". customerLogin und getCustomerPortalUser (= /me + Refresh) filtern representingFor jetzt zusätzlich über getAuthorizedCustomerIds() – nur Beziehungen mit isGranted=true landen im Token. MEDIUM – DTO-Leak in embedded Objekten: GET /customers/:id lieferte contracts[] mit commission/notes/ portalPasswordEncrypted/nextReviewDate; embedded customer in /contracts/:id zeigte notes. sanitizeCustomer(Strict) ruft jetzt sanitizeContract(Strict) auf jedes Element von contracts[] auf; `notes` ist als PORTAL_HIDDEN_CUSTOMER_FIELDS aufgenommen. LOW – /tasks?customerId=X gibt 200 mit leerem Array statt 403: Konsistenz-Fix: wenn Portal-User explizit nach customerId filtert, die er nicht vertreten darf → 403. Live-verifiziert: - Customer 1 vertritt 2+3 (Vollmachten widerrufen) → JWT representedCustomerIds=[], /me dito - Portal /customers/1.contracts[0]: keine Leaks; Admin sieht weiter commission/notes; portalPasswordEncrypted generell weg - Portal /tasks?customerId=2 → 403; /tasks?customerId=1 → 200 Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -97,6 +97,40 @@ isolierte Instanz (keine Multi-Tenancy im Code), Provisioning + Abrechnung
|
||||
|
||||
## ✅ Erledigt
|
||||
|
||||
- [x] **🚨 Pentest Runde 10 – Live-Vollmacht-Konsistenz + DTO-Leaks in embedded Objekten**
|
||||
- **MEDIUM – Stale Token nach Vollmacht-Widerruf**:
|
||||
Selbst ein FRISCHER Portal-Login lieferte JWT mit
|
||||
`representedCustomerIds: [7]` und `representedCustomers: [{Nina,…}]`,
|
||||
obwohl die Vollmacht widerrufen war. Live-Check beim Datenzugriff
|
||||
funktionierte (403), aber die UI zeigte dem Vertreter weiter, dass
|
||||
er Nina vertreten könne.
|
||||
* **Fix**: `customerLogin` und `getCustomerPortalUser` (= /me +
|
||||
Refresh-Pfad) filtern `representingFor` jetzt zusätzlich über
|
||||
`getAuthorizedCustomerIds()` – nur Beziehungen mit
|
||||
`isGranted: true` landen im Token und in /me.
|
||||
* Verifiziert: Customer 1 (vertritt 2,3 aber alle Vollmachten
|
||||
widerrufen) → JWT.representedCustomerIds = `[]`, /me ebenfalls.
|
||||
- **MEDIUM – DTO-Leak in embedded Objekten**:
|
||||
`GET /customers/:id` lieferte zwar Customer-Top-Level sanitisiert,
|
||||
aber `contracts[]` darin enthielt weiterhin `commission`, `notes`,
|
||||
`portalPasswordEncrypted`, `nextReviewDate`. Analog `notes` auf
|
||||
embedded customer in `/contracts/:id`.
|
||||
* **Fix**: `sanitizeCustomer(Strict)` ruft jetzt
|
||||
`sanitizeContract(Strict)` für jedes Element in `contracts[]`
|
||||
auf. `notes` zu `PORTAL_HIDDEN_CUSTOMER_FIELDS` ergänzt
|
||||
(interne CRM-Vermerke).
|
||||
* Verifiziert: Portal-User sieht in `customers/1.contracts[*]`
|
||||
keine commission/notes/PW-Encrypted/nextReviewDate mehr;
|
||||
Admin sieht sie weiterhin (Workflow-Bedarf);
|
||||
`portalPasswordEncrypted` ist generell entfernt (Klartext nur
|
||||
via `/contracts/:id/password` mit Audit-Log).
|
||||
- **LOW – `/tasks?customerId=X` 200 statt 403 für fremde IDs**:
|
||||
Konsistenz-Issue: nach Vollmacht-Widerruf gab der Endpoint
|
||||
leeres Array statt einen klaren 403-Fehler. Jetzt: wenn der
|
||||
Portal-User explizit nach einer customerId filtert, die er nicht
|
||||
(mehr) vertreten darf → 403 mit "Kein Zugriff auf diese
|
||||
Kundendaten". Verifiziert.
|
||||
|
||||
- [x] **🚨 Pentest Runde 7 (Anschlussrunde) – Information-Disclosure + Input-Validation**
|
||||
- **MEDIUM – Interne Felder in Portal-Responses**:
|
||||
* `sanitizeCustomerStrict` strippt jetzt zusätzlich
|
||||
|
||||
Reference in New Issue
Block a user