# Invalid IBAN structure rejected before submission

- **Pattern:** `ab-001444` (`finserv-form-validation.account-routing.iban-structure-validation`)
- **Severity:** medium
- **Lifecycle:** active
- **Last modified:** 2026-04-18
- **Canonical URL:** https://auditbuffet.com/patterns/ab-001444
- **License:** CC-BY-4.0 — attribute to AuditBuffet Pattern Catalog (https://auditbuffet.com/patterns/ab-001444)

## Why it matters

An IBAN regex that validates structure but skips the mod-97 checksum accepts the majority of typoed IBANs — the check digit exists precisely to catch single-digit and transposition errors. Without it, `GB82WEST12345698765432` and `GB82WEST12345698765431` are indistinguishable to the validator, but only one is real. OWASP A03 treats insufficient input validation as an injection-class failure; an unvalidated IBAN that reaches payment processing triggers a failed SWIFT/SEPA transfer, chargeback risk, and customer trust damage.

## Severity rationale

Medium because format-only IBAN validation lets typoed account numbers through to payment processing, causing failed transfers and chargeback exposure.

## Remediation

Extend the IBAN validator in `src/lib/validators.ts` to include the mod-97 step after format checking. The algorithm rearranges the IBAN and reduces it modulo 97.

```ts
// src/lib/validators.ts
export function validateIBAN(iban: string): boolean {
  iban = iban.replace(/\s/g, '').toUpperCase();
  if (!/^[A-Z]{2}\d{2}[A-Z0-9]{1,30}$/.test(iban)) return false;
  const rearranged = iban.slice(4) + iban.slice(0, 4);
  const numeric = rearranged.replace(/[A-Z]/g, c =>
    (c.charCodeAt(0) - 55).toString()
  );
  return BigInt(numeric) % 97n === 1n;
}
```

Wire this into the Zod schema used by `src/app/api/transfers/route.ts` via `.refine(validateIBAN, 'Invalid IBAN')`.

## Detection

- **ID:** `iban-structure-validation`
- **Severity:** `medium`
- **What to look for:** List all IBAN validation call sites. For each, classify whether the validation includes the mod-97 checksum algorithm or only checks format/length (regex-only). Quote the validation code found (e.g., `% 97n === 1n` or just `/^[A-Z]{2}\d{2}/`). Report: "X of Y IBAN validations include mod-97 checksum."
- **Pass criteria:** At least 100% of IBAN validation sites include the mod-97 checksum verification. Invalid IBANs that match the format pattern but fail the checksum are rejected before submission. No more than 0 IBAN validations rely on regex alone.
- **Fail criteria:** Any IBAN validation checks only format or length without the mod-97 checksum.
- **Must not pass when:** An IBAN regex like `/^[A-Z]{2}\d{2}[A-Z0-9]+$/` is the only validation — this accepts structurally invalid IBANs.
- **Skip (N/A) when:** The project does not use IBAN (US-only or other non-IBAN region, 0 IBAN references found).
- **Detail on fail:** `"IBAN validation only checks format (2 letters + 2 digits + alphanumeric). No mod-97 checksum validation — invalid IBANs would be accepted."`
- **Cross-reference:** The account-format-validation check in this category handles the broader region-routing logic; this check focuses specifically on IBAN checksum depth.
- **Remediation:** Add mod-97 checksum to the IBAN validator in `src/lib/validators.ts`. See the IBAN validation code in the account-format-validation check above.

## External references

- cwe CWE-20
- owasp A03

Taxons: injection-and-input-trust

HTML version: https://auditbuffet.com/patterns/ab-001444
