From e1ca56ebfb7a9152473bff54d550c0b66c26b72e Mon Sep 17 00:00:00 2001 From: duffyduck Date: Wed, 12 Aug 2026 11:44:52 +0200 Subject: [PATCH] getCockpit fail-closed (R146) - konsistent zu listAll/getContracts Pentester R146 (Konsistenz + Defense-in-Depth): getCockpit war der einzige der drei Geschwister-Endpunkte noch auf dem alten Fail-open- Muster. Der Cockpit-SERVICE ist bereits fail-closed (customerIds:[] -> IN () -> 0), aber der CONTROLLER uebergab bei falsy customerId undefined statt [] -> {} -> alle Kunden. Genau die R4-HIGH-Stelle 'Cockpit leakt alle Vertraege'. Jetzt: Portal-Token wird IMMER gescoped (ohne customerId -> []), identischer Einzeiler wie listAll/getContracts. Nicht erreichbar (Token traegt immer customerId), aber der urspruengliche HIGH-Endpunkt soll nicht das letzte Fail-open-Muster bleiben. Verifiziert: Cockpit Staff -> 17 Vertraege; Portal []->0 (contracts + cancellationConfirmations). Co-Authored-By: Claude Opus 4.8 (1M context) --- .../src/controllers/contract.controller.ts | 19 ++++++++++++------- 1 file changed, 12 insertions(+), 7 deletions(-) diff --git a/backend/src/controllers/contract.controller.ts b/backend/src/controllers/contract.controller.ts index 79787f29..13174d86 100644 --- a/backend/src/controllers/contract.controller.ts +++ b/backend/src/controllers/contract.controller.ts @@ -633,14 +633,19 @@ export async function getCockpit(req: AuthRequest, res: Response): Promise // Portal-User dürfen nur ihre eigenen + vertretene Kunden (mit Vollmacht) sehen. // Analog zu getContracts. Sonst leakt das Cockpit ALLE Verträge ALLER Kunden // (Pentest Runde 4, 2026-05-16: HOCH). + // Fail-closed (Pentest R146): Portal-Token wird IMMER gescoped; fehlt wider + // Erwarten die customerId, ergibt das eine leere Menge (nicht alle) – der + // Cockpit-Service filtert `customerIds: []` bereits als „keine Treffer". let customerIds: number[] | undefined; - if (req.user?.isCustomerPortal && req.user.customerId) { - customerIds = [req.user.customerId]; - const representedIds: number[] = req.user.representedCustomerIds || []; - for (const repCustId of representedIds) { - const hasAuth = await authorizationService.hasAuthorization(repCustId, req.user.customerId); - if (hasAuth) { - customerIds.push(repCustId); + if (req.user?.isCustomerPortal) { + customerIds = req.user.customerId ? [req.user.customerId] : []; + if (req.user.customerId) { + const representedIds: number[] = req.user.representedCustomerIds || []; + for (const repCustId of representedIds) { + const hasAuth = await authorizationService.hasAuthorization(repCustId, req.user.customerId); + if (hasAuth) { + customerIds.push(repCustId); + } } } }