From 8d93c1767b0588feefe58408e35ed054139ab623 Mon Sep 17 00:00:00 2001 From: duffyduck Date: Wed, 12 Aug 2026 11:32:21 +0200 Subject: [PATCH] R145-Hygiene: limit-Floor + getContracts fail-closed (Konsistenz) Pentester R145 (nice-to-have, keine Findings): 1) listAll: limit bekommt einen Floor (Math.max(1, ...)) - limit=-5 ergab vorher take:-5 an Prisma. page ebenso auf >=1 geklemmt. 2) getContracts nutzte dasselbe 'if (isCustomerPortal && customerId)'- Muster und war NICHT fail-closed. Jetzt konsistent zu listAll: - Controller: Portal-Token immer gescoped (ohne customerId -> []). - Service getAllContracts: 'if (customerIds)' statt '.length > 0', damit ein leeres Array strikt auf IN () filtert (0 Treffer) statt durchzufallen. Einziger Caller ist der Contract-Controller -> keine Regression fuer den Normalfall. Verifiziert: Staff -> alle; Portal customerIds=[] -> 0 Vertraege/Belege. Co-Authored-By: Claude Opus 4.8 (1M context) --- .../src/controllers/contract.controller.ts | 23 +++++++++++-------- .../src/controllers/creditNote.controller.ts | 4 ++-- backend/src/services/contract.service.ts | 7 ++++-- 3 files changed, 20 insertions(+), 14 deletions(-) diff --git a/backend/src/controllers/contract.controller.ts b/backend/src/controllers/contract.controller.ts index be91a850..79787f29 100644 --- a/backend/src/controllers/contract.controller.ts +++ b/backend/src/controllers/contract.controller.ts @@ -96,17 +96,20 @@ export async function getContracts(req: AuthRequest, res: Response): Promise { } } - const page = parseInt((req.query.page as string) || '1') || 1; - const limit = Math.min(parseInt((req.query.limit as string) || '50') || 50, 200); + const page = Math.max(parseInt((req.query.page as string) || '1') || 1, 1); + const limit = Math.min(Math.max(parseInt((req.query.limit as string) || '50') || 50, 1), 200); const search = typeof req.query.search === 'string' ? req.query.search : undefined; const result = await creditNoteService.getAllCreditNotes({ customerIds, page, limit, search }); diff --git a/backend/src/services/contract.service.ts b/backend/src/services/contract.service.ts index f2758f07..7ccd831b 100644 --- a/backend/src/services/contract.service.ts +++ b/backend/src/services/contract.service.ts @@ -21,8 +21,11 @@ export async function getAllContracts(filters: ContractFilters) { const where: Record = {}; - // Entweder einzelne customerId ODER Liste von customerIds (für Kundenportal) - if (customerIds && customerIds.length > 0) { + // Entweder Liste von customerIds (Kundenportal, fail-closed) ODER einzelne + // customerId (Staff-Filter). Fail-closed (Pentest R145): ist customerIds + // gesetzt – auch als LEERES Array – wird strikt darauf gefiltert (`IN ()` + // → 0 Treffer). Nur `undefined` (Staff) überspringt den Filter. + if (customerIds) { where.customerId = { in: customerIds }; } else if (customerId) { where.customerId = customerId;