Description
isTaxID(str, 'dk-DK') (Danish CPR-nummer) incorrectly rejects syntactically- and checksum-valid CPR numbers whenever the 7th digit (century digit) is 5, 6, 7, or 8 and the 2-digit birth year is in the range 37-58 (inclusive).
Root cause
In src/lib/isTaxID.js, dkDkCheck:
switch (century_digit) {
case '0':
case '1':
case '2':
case '3':
year = `19${year}`;
break;
case '4':
case '9':
if (year < 37) {
year = `20${year}`;
} else {
year = `19${year}`;
}
break;
default: // century_digit is 5, 6, 7, or 8
if (year < 37) {
year = `20${year}`;
} else if (year > 58) {
year = `18${year}`;
} else {
return false;
}
break;
}
The case '4': case '9': branch correctly implements the documented rule for those digits (year 00-36 → 2000s, year 37-99 → 1900s).
For century digits 5, 6, 7, 8, the documented rule (see e.g. the century-digit table reproduced at https://blog.ploeh.dk/2018/12/10/danish-cpr-numbers-in-f/, sourced from the Danish Wikipedia CPR-nummer article) is a clean split with no gap: year 00-57 → 2000s, year 58-99 → 1800s.
The default branch instead reuses the wrong thresholds from the 4/9 case (year < 37 / year > 58), leaving years 37-58 falling into an else { return false; } that shouldn't exist at all — every 2-digit year should map to exactly one century for these digit values.
Reproduction (validator 13.15.35, latest on npm at time of writing)
Using checksum-valid constructed CPR numbers (day=01, month=01, century digit 5, correct check digit computed with the library's own weight formula):
const v = require('validator');
console.log(v.isTaxID('0101365018', 'dk-DK')); // true (year 36, correct)
console.log(v.isTaxID('0101375005', 'dk-DK')); // false <-- should be true (Jan 1 2037)
console.log(v.isTaxID('0101455009', 'dk-DK')); // false <-- should be true (Jan 1 2045)
console.log(v.isTaxID('0101575004', 'dk-DK')); // false <-- should be true (Jan 1 2057)
console.log(v.isTaxID('0101585018', 'dk-DK')); // false <-- should be true (Jan 1 1858)
console.log(v.isTaxID('0101595005', 'dk-DK')); // true (year 59, correct: Jan 1 1859)
Impact
Any application using isTaxID(input, 'dk-DK') will wrongly reject legitimate CPR numbers for:
- People born in 1858 (year=58 exactly — a case the CPR system explicitly supports; its historical documentation cites a real 1863 registrant, so 1858 is within the system's intended range) — affects historical-records validation today.
- Anyone issued a CPR number with a birth year of 2037-2057 under century digits 5-8 — will start affecting real newly-issued numbers as those years arrive.
This is a false-negative / over-rejection bug, not a validation bypass — no security impact, just incorrect application behavior for the affected date ranges.
Suggested fix
default: // century_digit is 5, 6, 7, or 8
if (year <= 57) {
year = `20${year}`;
} else {
year = `18${year}`;
}
break;
This matches the documented gap-free century-digit table for 5-8 and removes the erroneous rejection branch.
Description
isTaxID(str, 'dk-DK')(Danish CPR-nummer) incorrectly rejects syntactically- and checksum-valid CPR numbers whenever the 7th digit (century digit) is5,6,7, or8and the 2-digit birth year is in the range37-58(inclusive).Root cause
In
src/lib/isTaxID.js,dkDkCheck:The
case '4': case '9':branch correctly implements the documented rule for those digits (year 00-36 → 2000s, year 37-99 → 1900s).For century digits 5, 6, 7, 8, the documented rule (see e.g. the century-digit table reproduced at https://blog.ploeh.dk/2018/12/10/danish-cpr-numbers-in-f/, sourced from the Danish Wikipedia CPR-nummer article) is a clean split with no gap: year 00-57 → 2000s, year 58-99 → 1800s.
The
defaultbranch instead reuses the wrong thresholds from the 4/9 case (year < 37/year > 58), leaving years 37-58 falling into anelse { return false; }that shouldn't exist at all — every 2-digit year should map to exactly one century for these digit values.Reproduction (validator 13.15.35, latest on npm at time of writing)
Using checksum-valid constructed CPR numbers (day=01, month=01, century digit 5, correct check digit computed with the library's own weight formula):
Impact
Any application using
isTaxID(input, 'dk-DK')will wrongly reject legitimate CPR numbers for:This is a false-negative / over-rejection bug, not a validation bypass — no security impact, just incorrect application behavior for the affected date ranges.
Suggested fix
This matches the documented gap-free century-digit table for 5-8 and removes the erroneous rejection branch.