Anzeige-Fix: HTML in providerName/tariffName etc. beim Read strippen
In der Vertragsübersicht tauchen rohe <script>/<img>-Payloads als Plaintext auf – React escaped sie zwar (kein XSS), sie sehen aber hässlich aus. Ursprung: Daten aus pre-Pentest-Zeit, bevor sanitizeContractBody beim Write existierte. Fix: sanitizeContract und sanitizeCustomer strippen jetzt zusätzlich HTML in den definierten Display-Feldern (providerName, tariffName, customerNumberAtProvider, firstName, lastName, companyName, etc.). Wirkt auch auf nested previousContract + energyDetails. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -59,6 +59,39 @@ const PORTAL_HIDDEN_CONTRACT_FIELDS = [
|
||||
'nextReviewDate', // Snooze-Workflow ist internes Cockpit-Feature
|
||||
] as const;
|
||||
|
||||
// User-eingabe String-Felder am Contract, die in der UI dargestellt werden.
|
||||
// Werden beim Read über stripHtml geschickt, damit Alt-Daten mit rohen
|
||||
// XSS-Payloads (vor Einführung von sanitizeContractBody) nicht mehr als
|
||||
// `<script>alert(...)</script>` in der Liste auftauchen. Neue Daten sind
|
||||
// schon beim Write gestrippt, aber doppelt hält besser.
|
||||
const CONTRACT_DISPLAY_STRING_FIELDS = [
|
||||
'providerName',
|
||||
'tariffName',
|
||||
'customerNumberAtProvider',
|
||||
'contractNumberAtProvider',
|
||||
'portalUsername',
|
||||
'previousProviderName',
|
||||
'previousCustomerNumber',
|
||||
'previousContractNumber',
|
||||
'notes',
|
||||
] as const;
|
||||
|
||||
// User-eingabe String-Felder am Customer für dieselbe Read-Time-Defensive.
|
||||
const CUSTOMER_DISPLAY_STRING_FIELDS = [
|
||||
'firstName',
|
||||
'lastName',
|
||||
'companyName',
|
||||
'salutation',
|
||||
'email',
|
||||
'phone',
|
||||
'mobile',
|
||||
'portalEmail',
|
||||
'portalUsername',
|
||||
'taxNumber',
|
||||
'commercialRegisterNumber',
|
||||
'notes',
|
||||
] as const;
|
||||
|
||||
const SENSITIVE_USER_FIELDS = [
|
||||
'password',
|
||||
'passwordResetToken',
|
||||
@@ -79,6 +112,11 @@ export function sanitizeCustomer<T extends Record<string, unknown>>(customer: T
|
||||
for (const field of SENSITIVE_CUSTOMER_FIELDS) {
|
||||
delete copy[field];
|
||||
}
|
||||
for (const field of CUSTOMER_DISPLAY_STRING_FIELDS) {
|
||||
if (typeof copy[field] === 'string') {
|
||||
copy[field] = stripHtml(copy[field]);
|
||||
}
|
||||
}
|
||||
if (Array.isArray(copy.contracts)) {
|
||||
copy.contracts = (copy.contracts as Record<string, unknown>[]).map((c) => sanitizeContract(c));
|
||||
}
|
||||
@@ -126,6 +164,25 @@ export function sanitizeContract<T extends Record<string, unknown>>(contract: T
|
||||
for (const field of SENSITIVE_CONTRACT_FIELDS) {
|
||||
delete copy[field];
|
||||
}
|
||||
for (const field of CONTRACT_DISPLAY_STRING_FIELDS) {
|
||||
if (typeof copy[field] === 'string') {
|
||||
copy[field] = stripHtml(copy[field]);
|
||||
}
|
||||
}
|
||||
// Nested: previousProviderName liegt im energyDetails-Sub-Objekt
|
||||
if (copy.energyDetails && typeof copy.energyDetails === 'object') {
|
||||
const ed = copy.energyDetails as Record<string, unknown>;
|
||||
if (typeof ed.previousProviderName === 'string') {
|
||||
ed.previousProviderName = stripHtml(ed.previousProviderName);
|
||||
}
|
||||
if (typeof ed.previousCustomerNumber === 'string') {
|
||||
ed.previousCustomerNumber = stripHtml(ed.previousCustomerNumber);
|
||||
}
|
||||
}
|
||||
// Nested: previousContract wird rekursiv auch sanitisiert
|
||||
if (copy.previousContract && typeof copy.previousContract === 'object') {
|
||||
copy.previousContract = sanitizeContract(copy.previousContract as Record<string, unknown>);
|
||||
}
|
||||
if (copy.customer && typeof copy.customer === 'object') {
|
||||
copy.customer = sanitizeCustomer(copy.customer as Record<string, unknown>);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user