Security-Hardening Runde 16: KRITISCH – Update-Responses sanitisieren
Pentest Runde 15: 20.3 KRITISCH: PUT /customers/:id gab portalPasswordHash (bcrypt $2a$12$…) im Response zurück. updateCustomer reichte das rohe Service-Output ohne sanitize-Aufruf durch. 20.4 HOCH (gleiche Klasse): PUT-Response leakte portalPasswordResetToken, portalPasswordMustChange, consentHash, portalTokenInvalidatedAt. Fix: - updateCustomer + createCustomer rufen sanitizeCustomer bzw. sanitizeCustomerStrict je nach customers:update-Permission. - updateContract + createContract + createFollowUp + createRenewal analog mit sanitizeContract / sanitizeContractStrict je nach isCustomerPortal. - portalPasswordMustChange + portalTokenInvalidatedAt von PORTAL_HIDDEN_CUSTOMER_FIELDS zu SENSITIVE_CUSTOMER_FIELDS hochgezogen → greift auch in normaler sanitizeCustomer (Admin-Sicht). Live-verifiziert: - Admin PUT /customers/3 → 0 Leaks von Hash/Token/Expires/MustChange/ consentHash/TokenInvalidatedAt; portalPasswordEncrypted bleibt für Admin sichtbar (UI-Workflow, separater Endpoint mit Audit) - POST /customers → 0 Leaks - Portal-User GET /customers/3 → 0 Leaks auch bei portalPasswordEncrypted/notes Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -109,7 +109,7 @@ export async function getContract(req: AuthRequest, res: Response): Promise<void
|
||||
}
|
||||
}
|
||||
|
||||
export async function createContract(req: Request, res: Response): Promise<void> {
|
||||
export async function createContract(req: AuthRequest, res: Response): Promise<void> {
|
||||
try {
|
||||
// Input-Validierung: type + customerId sind Pflicht, sonst stürzte der
|
||||
// Service mit einer kryptischen JS-Message ab (Pentest Runde 12, INFO).
|
||||
@@ -129,7 +129,9 @@ export async function createContract(req: Request, res: Response): Promise<void>
|
||||
label: `Vertrag ${contract.contractNumber} angelegt`,
|
||||
customerId: contract.customerId,
|
||||
});
|
||||
res.status(201).json({ success: true, data: contract } as ApiResponse);
|
||||
const isPortal = !!req.user?.isCustomerPortal;
|
||||
const sanitized = isPortal ? sanitizeContractStrict(contract as any) : sanitizeContract(contract as any);
|
||||
res.status(201).json({ success: true, data: sanitized } as ApiResponse);
|
||||
} catch (error) {
|
||||
res.status(400).json({
|
||||
success: false,
|
||||
@@ -201,7 +203,13 @@ export async function updateContract(req: AuthRequest, res: Response): Promise<v
|
||||
customerId: before?.customerId,
|
||||
});
|
||||
|
||||
res.json({ success: true, data: contract } as ApiResponse);
|
||||
// Response sanitisieren – sonst leakt portalPasswordEncrypted etc.
|
||||
// (Pentest Runde 15, gleiche Klasse wie 20.3 für Customer).
|
||||
const isPortal = !!req.user?.isCustomerPortal;
|
||||
const sanitized = isPortal
|
||||
? sanitizeContractStrict(contract as any)
|
||||
: sanitizeContract(contract as any);
|
||||
res.json({ success: true, data: sanitized } as ApiResponse);
|
||||
} catch (error) {
|
||||
res.status(400).json({
|
||||
success: false,
|
||||
@@ -270,7 +278,9 @@ export async function createFollowUp(req: AuthRequest, res: Response): Promise<v
|
||||
customerId: contract.customerId,
|
||||
});
|
||||
|
||||
res.status(201).json({ success: true, data: contract } as ApiResponse);
|
||||
const isPortal = !!req.user?.isCustomerPortal;
|
||||
const sanitized = isPortal ? sanitizeContractStrict(contract as any) : sanitizeContract(contract as any);
|
||||
res.status(201).json({ success: true, data: sanitized } as ApiResponse);
|
||||
} catch (error) {
|
||||
res.status(400).json({
|
||||
success: false,
|
||||
@@ -323,7 +333,9 @@ export async function createRenewal(req: AuthRequest, res: Response): Promise<vo
|
||||
customerId: contract.customerId,
|
||||
});
|
||||
|
||||
res.status(201).json({ success: true, data: contract } as ApiResponse);
|
||||
const isPortal = !!req.user?.isCustomerPortal;
|
||||
const sanitized = isPortal ? sanitizeContractStrict(contract as any) : sanitizeContract(contract as any);
|
||||
res.status(201).json({ success: true, data: sanitized } as ApiResponse);
|
||||
} catch (error) {
|
||||
res.status(400).json({
|
||||
success: false,
|
||||
|
||||
Reference in New Issue
Block a user