BLZ-Guard: vollstaendige Formatpruefung aller Eintraege (Pentest R148)
Der Poisoning-Guard pruefte bisher nur die erste current-Zeile - ein Set mit 1 echten + 999 Fake-Eintraegen kaeme durch. Jetzt wird JEDER Eintrag in current + next.upsert geprueft (8-stellige BLZ, Wert [Name] oder [Name,BIC]), next.remove auf 8-stellige BLZ, next.valid auf ein gueltiges Datum. Dabei korrekt beruecksichtigt: Banken ohne BIC haben nur [Name] (Laenge 1) - lookupBlz liefert dann bic:''. Real gegen den echten Datensatz verifiziert (3506 Eintraege, inkl. BIC-lose wie BLZ 60050009). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -35,7 +35,7 @@ const REGISTRY_BASE = process.env.BLZ_REGISTRY_BASE || 'https://registry.npmjs.o
|
||||
const FETCH_TIMEOUT_MS = 20_000;
|
||||
|
||||
// ---- Typen ----
|
||||
type BankTuple = [string, string]; // [bankName, bic]
|
||||
type BankTuple = [string, string?]; // [bankName, bic?] – BIC fehlt bei manchen Banken
|
||||
type CurrentData = Record<string, BankTuple>;
|
||||
interface NextData {
|
||||
valid: string;
|
||||
@@ -159,7 +159,7 @@ export function lookupBlz(blz: string): BankInfo | null {
|
||||
if (!/^[1-9]\d{7}$/.test(blz)) return null;
|
||||
const entry = combinedData()[blz];
|
||||
if (!entry) return null;
|
||||
return { bankName: entry[0], bic: entry[1], blz };
|
||||
return { bankName: entry[0], bic: entry[1] ?? '', blz };
|
||||
}
|
||||
|
||||
/** Extrahiert die BLZ aus einer deutschen IBAN und schlägt sie nach. */
|
||||
@@ -171,20 +171,53 @@ export function lookupByIban(iban: string): BankInfo | null {
|
||||
|
||||
// ---- Validierung des Roh-Datensatzes (gegen Müll/HTML-Antworten) ----
|
||||
|
||||
// Prüft eine BLZ→[Name,BIC]-Map vollständig: JEDER Schlüssel eine 8-stellige
|
||||
// BLZ, JEDER Wert genau [string, string] (BIC darf leer sein). Nicht nur die
|
||||
// erste Zeile (Pentest R148, Code-Note) – sonst käme ein Set mit 1 echten +
|
||||
// 999 Fake-Einträgen durch.
|
||||
function assertValidBankMap(map: Record<string, unknown>, label: string): void {
|
||||
for (const [blz, entry] of Object.entries(map)) {
|
||||
if (!/^\d{8}$/.test(blz)) {
|
||||
throw new Error(`${label}: ungültige BLZ "${blz}"`);
|
||||
}
|
||||
// Wert ist [Name] (Bank ohne BIC) ODER [Name, BIC]. Beides ist in der
|
||||
// echten Bundesbank-Datei vorhanden (z.B. reine Zahlungsverkehr-BLZ).
|
||||
if (
|
||||
!Array.isArray(entry) ||
|
||||
entry.length < 1 ||
|
||||
entry.length > 2 ||
|
||||
typeof entry[0] !== 'string' ||
|
||||
(entry.length === 2 && typeof entry[1] !== 'string')
|
||||
) {
|
||||
throw new Error(`${label}: Eintrag ${blz} hat unerwartetes Format`);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function assertValidRawDataset(raw: any): asserts raw is RawDataset {
|
||||
if (!raw || typeof raw !== 'object') throw new Error('Datensatz ist kein Objekt');
|
||||
const cur = raw.current;
|
||||
if (!cur || typeof cur !== 'object' || Array.isArray(cur)) throw new Error('current fehlt/ungültig');
|
||||
const keys = Object.keys(cur);
|
||||
if (keys.length < 1000) throw new Error(`current zu klein (${keys.length} Einträge)`);
|
||||
const sample = cur[keys[0]];
|
||||
if (!Array.isArray(sample) || sample.length !== 2 || typeof sample[0] !== 'string' || typeof sample[1] !== 'string') {
|
||||
throw new Error('current-Eintrag hat unerwartetes Format');
|
||||
}
|
||||
// VOLLSTÄNDIGE Formatprüfung aller Einträge, nicht nur des ersten.
|
||||
assertValidBankMap(cur, 'current');
|
||||
|
||||
const next = raw.next;
|
||||
if (!next || typeof next !== 'object' || typeof next.valid !== 'string' || typeof next.upsert !== 'object' || !Array.isArray(next.remove)) {
|
||||
if (!next || typeof next !== 'object' || typeof next.valid !== 'string' || typeof next.upsert !== 'object' || Array.isArray(next.upsert) || !Array.isArray(next.remove)) {
|
||||
throw new Error('next fehlt/ungültig');
|
||||
}
|
||||
// Datum plausibel?
|
||||
if (Number.isNaN(new Date(next.valid).getTime())) {
|
||||
throw new Error('next.valid ist kein gültiges Datum');
|
||||
}
|
||||
// upsert-Einträge ebenso streng; remove muss aus 8-stelligen BLZ bestehen.
|
||||
assertValidBankMap(next.upsert, 'next.upsert');
|
||||
for (const blz of next.remove) {
|
||||
if (typeof blz !== 'string' || !/^\d{8}$/.test(blz)) {
|
||||
throw new Error(`next.remove enthält ungültige BLZ "${blz}"`);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ---- Ausgehende Requests (nur Updater) ----
|
||||
|
||||
Reference in New Issue
Block a user