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) <noreply@anthropic.com>
This commit is contained in:
2026-08-12 11:44:52 +02:00
co-authored by Claude Opus 4.8
parent 8d93c1767b
commit e1ca56ebfb
+12 -7
View File
@@ -633,14 +633,19 @@ export async function getCockpit(req: AuthRequest, res: Response): Promise<void>
// Portal-User dürfen nur ihre eigenen + vertretene Kunden (mit Vollmacht) sehen. // Portal-User dürfen nur ihre eigenen + vertretene Kunden (mit Vollmacht) sehen.
// Analog zu getContracts. Sonst leakt das Cockpit ALLE Verträge ALLER Kunden // Analog zu getContracts. Sonst leakt das Cockpit ALLE Verträge ALLER Kunden
// (Pentest Runde 4, 2026-05-16: HOCH). // (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; let customerIds: number[] | undefined;
if (req.user?.isCustomerPortal && req.user.customerId) { if (req.user?.isCustomerPortal) {
customerIds = [req.user.customerId]; customerIds = req.user.customerId ? [req.user.customerId] : [];
const representedIds: number[] = req.user.representedCustomerIds || []; if (req.user.customerId) {
for (const repCustId of representedIds) { const representedIds: number[] = req.user.representedCustomerIds || [];
const hasAuth = await authorizationService.hasAuthorization(repCustId, req.user.customerId); for (const repCustId of representedIds) {
if (hasAuth) { const hasAuth = await authorizationService.hasAuthorization(repCustId, req.user.customerId);
customerIds.push(repCustId); if (hasAuth) {
customerIds.push(repCustId);
}
} }
} }
} }