/** * Einmal-Bereinigung für Pentest-Reste (Runde 12 / 2026-05-18): * * 1. HTML-Tags aus Customer/User-Stringfeldern strippen (M2-Stored-XSS-Reste) * 2. Unbekannte App-Settings entfernen, die durch Mass-Assignment in die DB * gerutscht sind, BEVOR die Whitelist eingezogen wurde (M1-Reste). * * Idempotent: wenn nichts zu tun ist, ändert sich nichts. Bei Bedarf * mehrfach aufrufbar. */ import prisma from '../src/lib/prisma.js'; import { stripHtml } from '../src/utils/sanitize.js'; import { ALLOWED_SETTING_KEYS } from '../src/services/appSetting.service.js'; const CUSTOMER_STRING_FIELDS = [ 'salutation', 'firstName', 'lastName', 'companyName', 'birthPlace', 'email', 'phone', 'mobile', 'taxNumber', 'commercialRegisterNumber', 'notes', ]; const USER_STRING_FIELDS = [ 'firstName', 'lastName', 'email', 'whatsappNumber', 'telegramUsername', 'signalNumber', ]; async function cleanupXss() { const customers = await prisma.customer.findMany(); let touched = 0; for (const c of customers) { const updates: Record = {}; for (const field of CUSTOMER_STRING_FIELDS) { const v = (c as any)[field]; if (typeof v === 'string') { const cleaned = stripHtml(v) as string; if (cleaned !== v) updates[field] = cleaned; } } if (Object.keys(updates).length > 0) { console.log(` Customer #${c.id}: bereinigt:`, Object.keys(updates).join(', ')); await prisma.customer.update({ where: { id: c.id }, data: updates }); touched++; } } console.log(` → Customer bereinigt: ${touched}`); const users = await prisma.user.findMany(); let userTouched = 0; for (const u of users) { const updates: Record = {}; for (const field of USER_STRING_FIELDS) { const v = (u as any)[field]; if (typeof v === 'string') { const cleaned = stripHtml(v) as string; if (cleaned !== v) updates[field] = cleaned; } } if (Object.keys(updates).length > 0) { console.log(` User #${u.id}: bereinigt:`, Object.keys(updates).join(', ')); await prisma.user.update({ where: { id: u.id }, data: updates }); userTouched++; } } console.log(` → User bereinigt: ${userTouched}`); } async function cleanupAppSettings() { const settings = await prisma.appSetting.findMany(); const removed: string[] = []; for (const s of settings) { if (!ALLOWED_SETTING_KEYS.has(s.key)) { removed.push(s.key); await prisma.appSetting.delete({ where: { key: s.key } }); } } console.log(` → AppSettings entfernt: ${removed.length}${removed.length ? ' (' + removed.join(', ') + ')' : ''}`); } // Pattern, die auf typische Pentest-/Test-Daten hindeuten. Bewusst eng // gefasst – legitime Kunden mit "Hacker" als Nachnamen sollen nicht // fälschlich getroffen werden (gibt's reichlich, gerade hier). // Konkret weggelassen: `^hacker@` würde Verwandte/Kunden mit // `hacker@familie-hacker.de` o.ä. fängen. const PENTEST_MARKERS = [ /@evil\./i, /^attacker@/i, /^pentest@/i, / /javascript:/i, // javascript:-URL /'\s*OR\s*'1'\s*=\s*'1/i, // SQL-Injection /\.\.\/.*etc\/passwd/i, // Path-Traversal ]; function looksLikePentestData(value: unknown): boolean { if (typeof value !== 'string') return false; return PENTEST_MARKERS.some((re) => re.test(value)); } async function findOrPurgePentestRecords() { const purge = process.env.CLEANUP_PURGE_PENTEST === 'true'; const suspect: Array<{ kind: string; id: number; reason: string }> = []; const customers = await prisma.customer.findMany(); for (const c of customers) { for (const f of ['email', 'phone', 'mobile', 'firstName', 'lastName', 'companyName', 'notes']) { if (looksLikePentestData((c as any)[f])) { suspect.push({ kind: 'Customer', id: c.id, reason: `${f}=${JSON.stringify((c as any)[f]).slice(0, 60)}` }); break; } } } const users = await prisma.user.findMany(); for (const u of users) { for (const f of ['email', 'firstName', 'lastName']) { if (looksLikePentestData((u as any)[f])) { suspect.push({ kind: 'User', id: u.id, reason: `${f}=${JSON.stringify((u as any)[f]).slice(0, 60)}` }); break; } } } if (suspect.length === 0) { console.log(' → Keine Pentest-Marker in Customer/User-Records gefunden.'); return; } console.log(` → ${suspect.length} verdächtige Records (Pentest-Marker):`); for (const s of suspect) { console.log(` [${s.kind}#${s.id}] ${s.reason}`); } if (!purge) { console.log(' ℹ️ Zum Löschen Container mit CLEANUP_PURGE_PENTEST=true neu starten,'); console.log(' oder Records manuell über adminer entfernen.'); return; } for (const s of suspect) { if (s.kind === 'Customer') { await prisma.customer.delete({ where: { id: s.id } }).catch((e: any) => { console.log(` [Customer#${s.id}] Löschen fehlgeschlagen: ${e.message?.slice(0, 80)}`); }); } else if (s.kind === 'User') { await prisma.user.delete({ where: { id: s.id } }).catch((e: any) => { console.log(` [User#${s.id}] Löschen fehlgeschlagen: ${e.message?.slice(0, 80)}`); }); } } console.log(` → ${suspect.length} verdächtige Records gelöscht.`); } async function main() { console.log('=== Cleanup: XSS-Reste + Mass-Assignment-AppSettings ==='); await cleanupXss(); await cleanupAppSettings(); await findOrPurgePentestRecords(); console.log('=== Fertig. ==='); } main() .catch((e) => { console.error('Cleanup fehlgeschlagen:', e); process.exit(1); }) .finally(async () => { await prisma.$disconnect(); });