ABA routing numbers embed a mod-10 checksum across all nine digits — a format-only regex like /^\d{9}$/ accepts the vast majority of structurally invalid routing numbers that would fail at the ACH network. Invalid routing numbers cause failed or misdirected ACH transactions, NSF fees, and customer support costs. NACHA Operating Rules require validation of routing-number integrity before origination, and OWASP A03 names improper input validation among the top application-security failure modes. The fix is a 10-line checksum function, not a library dependency.
High because routing numbers without checksum validation allow invalid bank identifiers that cause ACH transaction failures and potential misdirected fund transfers.
Add the ABA mod-10 checksum algorithm to src/lib/validators.ts and wire it into the Zod schema used by the server route. The digit weighting pattern is 3-7-1 repeated across three digit groups.
// src/lib/validators.ts
export function isValidABARoutingNumber(r: string): boolean {
if (!/^\d{9}$/.test(r)) return false;
const d = r.split('').map(Number);
const sum =
3 * (d[0] + d[3] + d[6]) +
7 * (d[1] + d[4] + d[7]) +
1 * (d[2] + d[5] + d[8]);
return sum % 10 === 0;
}
In src/lib/schemas.ts, add .refine(isValidABARoutingNumber, 'Invalid routing number') to the routing number field and apply the schema in the server-side API route handler.
ID: finserv-form-validation.currency-amount.routing-number-checksum
Severity: high
What to look for: Count all routing number input fields and validation call sites. For each, classify whether validation includes the ABA mod-10 checksum algorithm or only checks format/length. Quote the validation function or regex found (e.g., /^\d{9}$/ vs. a full checksum function). Check whether validation runs server-side. Report: "X of Y routing number validations include checksum verification."
Pass criteria: At least 100% of routing number acceptance points validate the ABA check digit (mod-10 checksum), not just format. Validation runs server-side in addition to any client-side check. Report even on pass: "X routing number fields found, all with server-side checksum validation."
Fail criteria: Any routing number input accepts values without checksum verification, or checksum validation exists only client-side.
Should not pass when: A regex-only check like /^\d{9}$/ is used without the mod-10 checksum — format checks alone accept invalid routing numbers.
Skip (N/A) when: The project does not accept US bank routing numbers (0 routing number fields found after searching src/, app/, components/).
Detail on fail: "Routing numbers accepted without ABA check digit validation. Invalid routing numbers could be submitted." or "Only client-side regex validation; no server-side check digit verification"
Cross-reference: The Account & Routing Formats category's account-format-validation check covers broader account number format validation per region.
Remediation: ABA routing numbers have a checksum. Add the validation in src/lib/validators.ts or equivalent:
ABA check digit validation in src/lib/validators.ts:
function validateABARoutingNumber(routingNumber) {
if (!/^\d{9}$/.test(routingNumber)) return false;
const digits = routingNumber.split('').map(Number);
const checksum = (
(digits[0] * 3 + digits[1] * 7 + digits[2] * 1) % 10 +
(digits[3] * 3 + digits[4] * 7 + digits[5] * 1) % 10 +
(digits[6] * 3 + digits[7] * 7 + digits[8] * 1) % 10
) % 10;
return checksum === 0;
}
// Usage: validateABARoutingNumber('123456789')
Zod validation in src/lib/schemas.ts:
const routingNumberSchema = z.string()
.regex(/^\d{9}$/, 'Routing number must be 9 digits')
.refine(validateABARoutingNumber, 'Invalid routing number');