Skip to content

isCreditCard: Mastercard regex anchoring bug accepts 4-digit and partial strings #2717

@zhangjiashuo-cs

Description

@zhangjiashuo-cs

Description

isCreditCard returns true for the 4-digit string "5108" (and many other short or partial strings) due to a regex anchoring bug in the Mastercard pattern.

Reproduction (validator 13.15.35, the latest version on npm at time of writing)

const v = require("validator");
console.log(v.isCreditCard("5108"));                              // true  — expected false
console.log(v.isCreditCard("5108", { provider: "mastercard" }));  // true  — expected false
console.log(v.isCreditCard("5105105105105100"));                  // true  — correct

Root cause

The Mastercard regex in src/lib/isCreditCard.js is:

mastercard: /^5[1-5][0-9]{2}|(222[1-9]|22[3-9][0-9]|2[3-6][0-9]{2}|27[01][0-9]|2720)[0-9]{12}$/

Because | has lower precedence than ^ and $ anchors, the regex engine actually parses this as the union of two patterns:

  1. ^5[1-5][0-9]{2} — start-anchored only, matches any string beginning with 51XX55XX regardless of length (so "5108", "5108foo", etc. all match).
  2. (222[1-9]|22[3-9][0-9]|2[3-6][0-9]{2}|27[01][0-9]|2720)[0-9]{12}$ — end-anchored only.

Result: format validation degenerates to "starts with 51-55 anywhere" or "ends with 2-series BIN + 12 digits anywhere".

The same shape (single-anchor due to | precedence) appears in a few other card regexes in this file; worth a quick audit.

Suggested fix

Wrap the alternation in a non-capturing group so both anchors bind to all branches:

- mastercard: /^5[1-5][0-9]{2}|(222[1-9]|22[3-9][0-9]|2[3-6][0-9]{2}|27[01][0-9]|2720)[0-9]{12}$/
+ mastercard: /^(?:5[1-5][0-9]{2}|222[1-9]|22[3-9][0-9]|2[3-6][0-9]{2}|27[01][0-9]|2720)[0-9]{12}$/

After the fix, isCreditCard("5108") correctly returns false, and the valid 16-digit cases continue to return true.

Property that fails

// All credit-card-valid strings must have length within the documented per-issuer length ranges
// (Mastercard: 16; including 19-digit private/extension forms it's still ≥ 13).
function prop(s) {
  if (v.isCreditCard(s, { provider: "mastercard" })) {
    return s.length >= 13;  // every passing input must be at least 13 chars
  }
  return true;
}
prop("5108");  // false — invariant violated

Environment

  • validator: 13.15.35 (also reproduces in 13.12.0, so likely all 13.x)
  • Node: 20+

Happy to submit a PR if helpful.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions