Skip to content

fix(isLicensePlate): anchor pt-BR alternation to reject partial strings#2782

Open
Jordan-Bourillot wants to merge 1 commit into
validatorjs:masterfrom
Jordan-Bourillot:fix/license-plate-pt-br-anchoring
Open

fix(isLicensePlate): anchor pt-BR alternation to reject partial strings#2782
Jordan-Bourillot wants to merge 1 commit into
validatorjs:masterfrom
Jordan-Bourillot:fix/license-plate-pt-br-anchoring

Conversation

@Jordan-Bourillot

Copy link
Copy Markdown

Description

isLicensePlate(str, 'pt-BR') returns true for strings that contain a valid Brazilian plate but are padded with arbitrary leading or trailing characters. The format check is bypassed by an unanchored alternation in the regex.

Reproduction (validator 13.15.x)

const validator = require('validator');

validator.isLicensePlate('ABC1D23XYZ', 'pt-BR'); // true — expected false (trailing junk)
validator.isLicensePlate('ABC1D2345',  'pt-BR'); // true — expected false (trailing junk)
validator.isLicensePlate('XYZABC1234', 'pt-BR'); // true — expected false (leading junk)
validator.isLicensePlate('fooABC1234', 'pt-BR'); // true — expected false (leading junk)

The same false positives are reachable through isLicensePlate(str, 'any'), which returns true as soon as any locale matches.

Root cause

The 'pt-BR' pattern is:

/^[A-Z]{3}[ -]?[0-9][A-Z][0-9]{2}|[A-Z]{3}[ -]?[0-9]{4}$/

| has lower precedence than the ^ and $ anchors, so the engine reads this as the union of two half-anchored branches:

  1. ^[A-Z]{3}[ -]?[0-9][A-Z][0-9]{2} — start-anchored only → matches anything that begins with a Mercosur plate (trailing characters ignored).
  2. [A-Z]{3}[ -]?[0-9]{4}$ — end-anchored only → matches anything that ends with an old-format plate (leading characters ignored).

Fix

Wrap the alternation in a non-capturing group so both anchors apply to every branch:

-    /^[A-Z]{3}[ -]?[0-9][A-Z][0-9]{2}|[A-Z]{3}[ -]?[0-9]{4}$/.test(str),
+    /^(?:[A-Z]{3}[ -]?[0-9][A-Z][0-9]{2}|[A-Z]{3}[ -]?[0-9]{4})$/.test(str),

This is purely an anchoring change — the two format branches (the Mercosur ABC1D23 form and the older ABC1234 / ABC-1234 / ABC 1234 form) are untouched, so every previously valid plate still validates. It is the same class of bug recently fixed for isISO8601 in #2774.

Tests

Added the four padded inputs above to the existing pt-BR invalid fixtures in test/validators.test.js. They fail on master and pass with this change; all existing pt-BR valid/invalid fixtures are unchanged and the full suite passes (318 passing).

@codecov

codecov Bot commented Jun 23, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 100.00%. Comparing base (3d2f4b3) to head (601ce6c).

Additional details and impacted files
@@            Coverage Diff            @@
##            master     #2782   +/-   ##
=========================================
  Coverage   100.00%   100.00%           
=========================================
  Files          114       114           
  Lines         2587      2587           
  Branches       656       656           
=========================================
  Hits          2587      2587           

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

The 'pt-BR' pattern placed an un-grouped alternation between the ^ and $
anchors:

  /^[A-Z]{3}[ -]?[0-9][A-Z][0-9]{2}|[A-Z]{3}[ -]?[0-9]{4}$/

Because | has lower precedence than the anchors, the engine reads this
as two half-anchored branches — ^A (start-anchored only) and B$
(end-anchored only). The validator therefore accepted any string that
merely starts with a Mercosur plate or ends with an old-format plate.
For example isLicensePlate('ABC1D23XYZ', 'pt-BR') and
isLicensePlate('fooABC1234', 'pt-BR') both returned true.

Wrapping the alternation in a non-capturing group makes both anchors
apply to every branch. All previously valid plates still pass; the added
tests cover leading/trailing-junk inputs that were wrongly accepted.
@Jordan-Bourillot Jordan-Bourillot force-pushed the fix/license-plate-pt-br-anchoring branch from 7eabd58 to 601ce6c Compare June 23, 2026 22:20
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant