diff --git a/backend/scripts/seed-magic-byte-test.ts b/backend/scripts/seed-magic-byte-test.ts new file mode 100644 index 00000000..e7e07376 --- /dev/null +++ b/backend/scripts/seed-magic-byte-test.ts @@ -0,0 +1,146 @@ +/** + * Pentester-Hilfsscript (Pentest R152): legt einen KONTROLLIERTEN „getarnten" + * Upload an, um den Magic-Byte-Mismatch→attachment-Zweig des Download-Endpoints + * live zu belegen. + * + * Was passiert: + * - Es wird eine dedizierte TEST-Bankkarte (accountHolder-Marker) angelegt – + * ECHTE Kundendaten werden NICHT angefasst/überschrieben. + * - An diese Karte wird eine Datei mit `.pdf`-Endung gehängt, deren Inhalt + * aber ein SVG mit + +`; + +function uploadsDir(): string { + return path.join(process.cwd(), 'uploads', UPLOAD_SUBDIR); +} + +function absoluteFilePath(): string { + return path.join(uploadsDir(), FILENAME); +} + +async function create(customerIdArg?: number): Promise { + // Ziel-Customer bestimmen (Default: erster vorhandener Kunde). + const customer = customerIdArg + ? await prisma.customer.findUnique({ where: { id: customerIdArg }, select: { id: true, customerNumber: true } }) + : await prisma.customer.findFirst({ orderBy: { id: 'asc' }, select: { id: true, customerNumber: true } }); + + if (!customer) { + console.error('Kein Kunde gefunden – bitte zuerst einen Kunden anlegen oder --customer angeben.'); + process.exit(1); + } + + // Getarnte Datei schreiben. + fs.mkdirSync(uploadsDir(), { recursive: true }); + fs.writeFileSync(absoluteFilePath(), SVG_XSS, 'utf8'); + + // Test-Bankkarte anlegen oder wiederverwenden (idempotent, kein Duplikat). + const existing = await prisma.bankCard.findFirst({ where: { accountHolder: MARKER } }); + const card = existing + ? await prisma.bankCard.update({ where: { id: existing.id }, data: { customerId: customer.id, documentPath: RELATIVE_PATH } }) + : await prisma.bankCard.create({ + data: { + customerId: customer.id, + accountHolder: MARKER, + iban: 'DE00000000000000000000', + documentPath: RELATIVE_PATH, + description: 'Pentest R152 – getarnte Datei (SVG-als-PDF). Nach Test via cleanup entfernen.', + }, + }); + + const head = Buffer.alloc(12); + const fd = fs.openSync(absoluteFilePath(), 'r'); + fs.readSync(fd, head, 0, 12, 0); + fs.closeSync(fd); + + console.log('=== Magic-Byte-Test angelegt ==='); + console.log('Test-Customer :', customer.customerNumber, `(id ${customer.id})`); + console.log('Test-Bankkarte : id', card.id, `– accountHolder="${MARKER}"`); + console.log('Datei (Disk) :', absoluteFilePath()); + console.log('Erste 12 Bytes :', JSON.stringify(head.toString('latin1')), '(kein PDF/PNG/JPEG/GIF/WebP-Magic)'); + console.log('documentPath :', RELATIVE_PATH); + console.log(''); + console.log('--- So testen (Token des berechtigten Users anhängen) ---'); + console.log(` curl -sSI "https:///api/files/download?path=${RELATIVE_PATH}&disposition=inline&token="`); + console.log(' Erwartet: 200, Content-Disposition: attachment, X-Content-Type-Options: nosniff'); + console.log(' (NICHT inline) + Backend-Log "Magic-Byte-Check fehlgeschlagen".'); + console.log(` Ohne &disposition=inline: ebenfalls attachment.`); + console.log(' Als fremder Portal-User: 403 (Ownership-Check).'); + console.log(''); + console.log('>>> Nach dem Test aufräumen: npx tsx scripts/seed-magic-byte-test.ts cleanup'); +} + +async function cleanup(): Promise { + let removed = 0; + const cards = await prisma.bankCard.findMany({ where: { accountHolder: MARKER } }); + for (const c of cards) { + await prisma.bankCard.delete({ where: { id: c.id } }); + removed++; + } + // Datei löschen (falls vorhanden). + const abs = absoluteFilePath(); + let fileDeleted = false; + if (fs.existsSync(abs)) { + fs.unlinkSync(abs); + fileDeleted = true; + } + console.log('=== Cleanup ==='); + console.log('Gelöschte Test-Bankkarten:', removed); + console.log('Datei gelöscht :', fileDeleted ? abs : '(nicht vorhanden)'); +} + +(async () => { + const mode = process.argv[2] || 'create'; + const custIdx = process.argv.indexOf('--customer'); + const customerIdArg = custIdx >= 0 ? parseInt(process.argv[custIdx + 1], 10) : undefined; + try { + if (mode === 'create') { + await create(Number.isFinite(customerIdArg as number) ? customerIdArg : undefined); + } else if (mode === 'cleanup') { + await cleanup(); + } else { + console.error(`Unbekannter Modus "${mode}". Nutze: create | cleanup`); + process.exit(1); + } + } catch (err) { + console.error('Fehler:', err instanceof Error ? err.message : err); + process.exit(1); + } finally { + await prisma.$disconnect(); + } +})();