From 243f6c5fe467d464deff1981275e9fc4403e84f9 Mon Sep 17 00:00:00 2001 From: Gabriel Bouyssou Date: Sat, 21 Jun 2025 06:16:23 +1000 Subject: [PATCH 1/8] docs(isMACAddress): improve ambiguous option description (#2563) --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 592e661bf..366036d43 100644 --- a/README.md +++ b/README.md @@ -145,7 +145,7 @@ Validator | Description **isLocale(str)** | check if the string is a locale. **isLowercase(str)** | check if the string is lowercase. **isLuhnNumber(str)** | check if the string passes the [Luhn algorithm check](https://en.wikipedia.org/wiki/Luhn_algorithm). -**isMACAddress(str [, options])** | check if the string is a MAC address.

`options` is an object which defaults to `{ no_separators: false }`. If `no_separators` is true, the validator will allow MAC addresses without separators. Also, it allows the use of hyphens, spaces or dots e.g. '01 02 03 04 05 ab', '01-02-03-04-05-ab' or '0102.0304.05ab'. The options also allow a `eui` property to specify if it needs to be validated against EUI-48 or EUI-64. The accepted values of `eui` are: 48, 64. +**isMACAddress(str [, options])** | check if the string is a MAC address.

`options` is an object which defaults to `{ no_separators: false }`. It allows the use of hyphens, spaces or dots e.g. '01 02 03 04 05 ab', '01-02-03-04-05-ab' or '0102.0304.05ab'. If `no_separators` is true, the validator will then only check MAC addresses without separators. The options also allow a `eui` property to specify if it needs to be validated against EUI-48 or EUI-64. The accepted values of `eui` are: 48, 64. **isMagnetURI(str)** | check if the string is a [Magnet URI format][Magnet URI Format]. **isMailtoURI(str, [, options])** | check if the string is a [Mailto URI format][Mailto URI Format].

`options` is an object of validating emails inside the URI (check `isEmail`s options for details). **isMD5(str)** | check if the string is a MD5 hash.

Please note that you can also use the `isHash(str, 'md5')` function. Keep in mind that MD5 has some collision weaknesses compared to other algorithms (e.g., SHA). From 72573b3d1d8ab2e6575e6bba1cbe2b01f95f4935 Mon Sep 17 00:00:00 2001 From: Ward Khaddour Date: Mon, 23 Jun 2025 00:48:09 +0300 Subject: [PATCH 2/8] Add Qatar phone number validation (#2556) Co-authored-by: Rubin Bhandari --- src/lib/isMobilePhone.js | 1 + test/validators.test.js | 5 +++++ 2 files changed, 6 insertions(+) diff --git a/src/lib/isMobilePhone.js b/src/lib/isMobilePhone.js index ec1483d65..b00391ea6 100644 --- a/src/lib/isMobilePhone.js +++ b/src/lib/isMobilePhone.js @@ -20,6 +20,7 @@ const phones = { 'ar-SY': /^(!?(\+?963)|0)?9\d{8}$/, 'ar-TN': /^(\+?216)?[2459]\d{7}$/, 'az-AZ': /^(\+994|0)(10|5[015]|7[07]|99)\d{7}$/, + 'ar-QA': /^(\+?974|0)?([3567]\d{7})$/, 'bs-BA': /^((((\+|00)3876)|06))((([0-3]|[5-6])\d{6})|(4\d{7}))$/, 'be-BY': /^(\+?375)?(24|25|29|33|44)\d{7}$/, 'bg-BG': /^(\+?359|0)?8[789]\d{7}$/, diff --git a/test/validators.test.js b/test/validators.test.js index 7c8c49594..299af27d8 100644 --- a/test/validators.test.js +++ b/test/validators.test.js @@ -10688,6 +10688,11 @@ describe('Validators', () => { '80912345', ], }, + { + locale: 'ar-QA', + valid: ['+97435551234', '+97455551234', '+97465551234', '+97475551234', '35551234', '55551234', '65551234', '75551234'], + invalid: ['+97445551234', '+97405551234', '+9745555123', '+974555512345', '+97355551234', '+9125551234', '25551234', '+13005551234', '45551234', '95551234', '+9745555abcd', '', '+974'], + }, ]; let allValid = []; From abcc8ecb8569b531f8951d9f6343d2b156268e0c Mon Sep 17 00:00:00 2001 From: AVADOOTHA RAJESH <148096139+avadootharajesh@users.noreply.github.com> Date: Tue, 5 Aug 2025 16:24:51 +0530 Subject: [PATCH 3/8] feat(isAlpha, isAlphanumeric): add support for Indic locales (ta-IN, te-IN, kn-IN, ml-IN, gu-IN, pa-IN, or-IN) (#2576) --- README.md | 4 +- src/lib/alpha.js | 19 ++- test/validators.test.js | 252 ++++++++++++++++++++++++++++++++++++++++ 3 files changed, 271 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 366036d43..a0ea7a65d 100644 --- a/README.md +++ b/README.md @@ -88,8 +88,8 @@ Validator | Description **equals(str, comparison)** | check if the string matches the comparison. **isAbaRouting(str)** | check if the string is an ABA routing number for US bank account / cheque. **isAfter(str [, options])** | check if the string is a date that is after the specified date.

`options` is an object that defaults to `{ comparisonDate: Date().toString() }`.
**Options:**
`comparisonDate`: Date to compare to. Defaults to `Date().toString()` (now). -**isAlpha(str [, locale, options])** | check if the string contains only letters (a-zA-Z).

`locale` is one of `['ar', 'ar-AE', 'ar-BH', 'ar-DZ', 'ar-EG', 'ar-IQ', 'ar-JO', 'ar-KW', 'ar-LB', 'ar-LY', 'ar-MA', 'ar-QA', 'ar-QM', 'ar-SA', 'ar-SD', 'ar-SY', 'ar-TN', 'ar-YE', 'bg-BG', 'bn', 'cs-CZ', 'da-DK', 'de-DE', 'el-GR', 'en-AU', 'en-GB', 'en-HK', 'en-IN', 'en-NZ', 'en-US', 'en-ZA', 'en-ZM', 'eo', 'es-ES', 'fa-IR', 'fi-FI', 'fr-CA', 'fr-FR', 'he', 'hi-IN', 'hu-HU', 'it-IT', 'kk-KZ', 'ko-KR', 'ja-JP', 'ku-IQ', 'nb-NO', 'nl-NL', 'nn-NO', 'pl-PL', 'pt-BR', 'pt-PT', 'ru-RU', 'si-LK', 'sl-SI', 'sk-SK', 'sr-RS', 'sr-RS@latin', 'sv-SE', 'th-TH', 'tr-TR', 'uk-UA']` and defaults to `en-US`. Locale list is `validator.isAlphaLocales`. `options` is an optional object that can be supplied with the following key(s): `ignore` which can either be a String or RegExp of characters to be ignored e.g. " -" will ignore spaces and -'s. -**isAlphanumeric(str [, locale, options])** | check if the string contains only letters and numbers (a-zA-Z0-9).

`locale` is one of `['ar', 'ar-AE', 'ar-BH', 'ar-DZ', 'ar-EG', 'ar-IQ', 'ar-JO', 'ar-KW', 'ar-LB', 'ar-LY', 'ar-MA', 'ar-QA', 'ar-QM', 'ar-SA', 'ar-SD', 'ar-SY', 'ar-TN', 'ar-YE', 'bn', 'bg-BG', 'cs-CZ', 'da-DK', 'de-DE', 'el-GR', 'en-AU', 'en-GB', 'en-HK', 'en-IN', 'en-NZ', 'en-US', 'en-ZA', 'en-ZM', 'eo', 'es-ES', 'fa-IR', 'fi-FI', 'fr-CA', 'fr-FR', 'he', 'hi-IN', 'hu-HU', 'it-IT', 'kk-KZ', 'ko-KR', 'ja-JP','ku-IQ', 'nb-NO', 'nl-NL', 'nn-NO', 'pl-PL', 'pt-BR', 'pt-PT', 'ru-RU', 'si-LK', 'sl-SI', 'sk-SK', 'sr-RS', 'sr-RS@latin', 'sv-SE', 'th-TH', 'tr-TR', 'uk-UA']`) and defaults to `en-US`. Locale list is `validator.isAlphanumericLocales`. `options` is an optional object that can be supplied with the following key(s): `ignore` which can either be a String or RegExp of characters to be ignored e.g. " -" will ignore spaces and -'s. +**isAlpha(str [, locale, options])** | check if the string contains only letters (a-zA-Z).

`locale` is one of `['ar', 'ar-AE', 'ar-BH', 'ar-DZ', 'ar-EG', 'ar-IQ', 'ar-JO', 'ar-KW', 'ar-LB', 'ar-LY', 'ar-MA', 'ar-QA', 'ar-QM', 'ar-SA', 'ar-SD', 'ar-SY', 'ar-TN', 'ar-YE', 'bg-BG', 'bn', 'bn-IN', 'cs-CZ', 'da-DK', 'de-DE', 'el-GR', 'en-AU', 'en-GB', 'en-HK', 'en-IN', 'en-NZ', 'en-US', 'en-ZA', 'en-ZM', 'eo', 'es-ES', 'fa-IR', 'fi-FI', 'fr-CA', 'fr-FR', 'gu-IN', 'he', 'hi-IN', 'hu-HU', 'it-IT', 'ja-JP', 'kk-KZ', 'kn-IN', 'ko-KR', 'ku-IQ', 'ml-IN', 'nb-NO', 'nl-NL', 'nn-NO', 'or-IN', 'pa-IN', 'pl-PL', 'pt-BR', 'pt-PT', 'ru-RU', 'si-LK', 'sk-SK', 'sl-SI', 'sr-RS', 'sr-RS@latin', 'sv-SE', 'ta-IN', 'te-IN', 'th-TH', 'tr-TR', 'uk-UA']` and defaults to `en-US`. Locale list is `validator.isAlphaLocales`. `options` is an optional object that can be supplied with the following key(s): `ignore` which can either be a String or RegExp of characters to be ignored e.g. " -" will ignore spaces and -'s. +**isAlphanumeric(str [, locale, options])** | check if the string contains only letters and numbers (a-zA-Z0-9).

`locale` is one of `['ar', 'ar-AE', 'ar-BH', 'ar-DZ', 'ar-EG', 'ar-IQ', 'ar-JO', 'ar-KW', 'ar-LB', 'ar-LY', 'ar-MA', 'ar-QA', 'ar-QM', 'ar-SA', 'ar-SD', 'ar-SY', 'ar-TN', 'ar-YE', 'bg-BG', 'bn', 'bn-IN', 'cs-CZ', 'da-DK', 'de-DE', 'el-GR', 'en-AU', 'en-GB', 'en-HK', 'en-IN', 'en-NZ', 'en-US', 'en-ZA', 'en-ZM', 'eo', 'es-ES', 'fa-IR', 'fi-FI', 'fr-CA', 'fr-FR', 'gu-IN', 'he', 'hi-IN', 'hu-HU', 'it-IT', 'ja-JP', 'kk-KZ', 'kn-IN', 'ko-KR', 'ku-IQ', 'ml-IN', 'nb-NO', 'nl-NL', 'nn-NO', 'or-IN', 'pa-IN', 'pl-PL', 'pt-BR', 'pt-PT', 'ru-RU', 'si-LK', 'sk-SK', 'sl-SI', 'sr-RS', 'sr-RS@latin', 'sv-SE', 'ta-IN', 'te-IN', 'th-TH', 'tr-TR', 'uk-UA']`) and defaults to `en-US`. Locale list is `validator.isAlphanumericLocales`. `options` is an optional object that can be supplied with the following key(s): `ignore` which can either be a String or RegExp of characters to be ignored e.g. " -" will ignore spaces and -'s. **isAscii(str)** | check if the string contains ASCII chars only. **isBase32(str [, options])** | check if the string is base32 encoded. `options` is optional and defaults to `{ crockford: false }`.
When `crockford` is true it tests the given base32 encoded string using [Crockford's base32 alternative][Crockford Base32]. **isBase58(str)** | check if the string is base58 encoded. diff --git a/src/lib/alpha.js b/src/lib/alpha.js index 8c37934ff..6f58c9aee 100644 --- a/src/lib/alpha.js +++ b/src/lib/alpha.js @@ -38,6 +38,13 @@ export const alpha = { eo: /^[ABCĈD-GĜHĤIJĴK-PRSŜTUŬVZ]+$/i, 'hi-IN': /^[\u0900-\u0961]+[\u0972-\u097F]*$/i, 'si-LK': /^[\u0D80-\u0DFF]+$/, + 'ta-IN': /^[\u0B80-\u0BFF]+$/i, + 'te-IN': /^[\u0C00-\u0C7F]+$/i, + 'kn-IN': /^[\u0C80-\u0CFF]+$/i, + 'ml-IN': /^[\u0D00-\u0D7F]+$/i, + 'gu-IN': /^[\u0A80-\u0AFF]+$/i, + 'pa-IN': /^[\u0A00-\u0A7F]+$/i, + 'or-IN': /^[\u0B00-\u0B7F]+$/i, }; export const alphanumeric = { @@ -79,6 +86,13 @@ export const alphanumeric = { eo: /^[0-9ABCĈD-GĜHĤIJĴK-PRSŜTUŬVZ]+$/i, 'hi-IN': /^[\u0900-\u0963]+[\u0966-\u097F]*$/i, 'si-LK': /^[0-9\u0D80-\u0DFF]+$/, + 'ta-IN': /^[0-9\u0B80-\u0BFF.]+$/i, + 'te-IN': /^[0-9\u0C00-\u0C7F.]+$/i, + 'kn-IN': /^[0-9\u0C80-\u0CFF.]+$/i, + 'ml-IN': /^[0-9\u0D00-\u0D7F.]+$/i, + 'gu-IN': /^[0-9\u0A80-\u0AFF.]+$/i, + 'pa-IN': /^[0-9\u0A00-\u0A7F.]+$/i, + 'or-IN': /^[0-9\u0B00-\u0B7F.]+$/i, }; export const decimal = { @@ -128,8 +142,9 @@ for (let locale, i = 0; i < bengaliLocales.length; i++) { export const dotDecimal = ['ar-EG', 'ar-LB', 'ar-LY']; export const commaDecimal = [ 'bg-BG', 'cs-CZ', 'da-DK', 'de-DE', 'el-GR', 'en-ZM', 'eo', 'es-ES', 'fr-CA', 'fr-FR', - 'id-ID', 'it-IT', 'ku-IQ', 'hi-IN', 'hu-HU', 'nb-NO', 'nn-NO', 'nl-NL', 'pl-PL', 'pt-PT', - 'ru-RU', 'kk-KZ', 'si-LK', 'sl-SI', 'sr-RS@latin', 'sr-RS', 'sv-SE', 'tr-TR', 'uk-UA', 'vi-VN', + 'gu-IN', 'hi-IN', 'hu-HU', 'id-ID', 'it-IT', 'kk-KZ', 'kn-IN', 'ku-IQ', 'ml-IN', 'nb-NO', + 'nl-NL', 'nn-NO', 'or-IN', 'pa-IN', 'pl-PL', 'pt-PT', 'ru-RU', 'si-LK', 'sl-SI', 'sr-RS', + 'sr-RS@latin', 'sv-SE', 'ta-IN', 'te-IN', 'tr-TR', 'uk-UA', 'vi-VN', ]; for (let i = 0; i < dotDecimal.length; i++) { diff --git a/test/validators.test.js b/test/validators.test.js index 299af27d8..f73a48164 100644 --- a/test/validators.test.js +++ b/test/validators.test.js @@ -1902,6 +1902,100 @@ describe('Validators', () => { }); }); + it('should validate Tamil alpha strings', () => { + test({ + validator: 'isAlpha', + args: ['ta-IN'], + valid: [ + 'அஆஇஈஉஊஎஏஐஒஓஔகஙசஞடணதநபமயரலவழளறனஶஜஷஸஹ', + 'தமிழ்', + ], + invalid: [ + 'தமிழ்123', + 'தமிழ் ', + 'தமிழ்.', + 'abc', + '', + ], + }); + }); + it('should validate Telugu alpha strings', () => { + test({ + validator: 'isAlpha', + args: ['te-IN'], + valid: [ + 'అఆఇఈఉఊఋఌఎఏఐఒఓఔకఖగఘఙచఛజఝఞటఠడఢణతథదధనపఫబభమయరలవశషసహ', + 'తెలుగు', + ], + invalid: ['తెలుగు123', 'తెలుగు.', 'abc', ''], + }); + }); + it('should validate Kannada alpha strings', () => { + test({ + validator: 'isAlpha', + args: ['kn-IN'], + valid: [ + 'ಅಆಇಈಉಊಋಎಏಐಒಓಔಕಖಗಘಙಚಛಜಝಞಟಠಡಢಣತಥದಧನಪಫಬಭಮಯರಲವಶಷಸಹಳ', + 'ಕನ್ನಡ', + ], + invalid: ['ಕನ್ನಡ123', 'ಕನ್ನಡ.', 'abc', ''], + }); + }); + it('should validate Malayalam alpha strings', () => { + test({ + validator: 'isAlpha', + args: ['ml-IN'], + valid: [ + 'അആഇഈഉഊഋഎഏഐഒഓഔകഖഗഘങചഛജഝഞടഠഡഢണതഥദധനപഫബഭമയരലവശഷസഹള', + 'മലയാളം', + ], + invalid: ['മലയാളം123', 'മലയാളം.', 'abc', ''], + }); + }); + it('should validate Gujarati alpha strings', () => { + test({ + validator: 'isAlpha', + args: ['gu-IN'], + valid: [ + 'અઆઇઈઉઊઋએઐઓઔકખગઘચછજઝટઠડઢણતથદધનપફબભમયરલવશષસહળ', + 'ગુજરાતી', + ], + invalid: ['ગુજરાતી123', 'ગુજરાતી.', 'abc', ''], + }); + }); + it('should validate Punjabi alpha strings', () => { + test({ + validator: 'isAlpha', + args: ['pa-IN'], + valid: [ + 'ਅਆਇਈਉਊਏਐਓਔਕਖਗਘਙਚਛਜਝਞਟਠਡਢਣਤਥਦਧਨਪਫਬਭਮਯਰਲਵਸ਼ਸਹ', + 'ਪੰਜਾਬੀ', + ], + invalid: ['ਪੰਜਾਬੀ123', 'ਪੰਜਾਬੀ.', 'abc', ''], + }); + }); + it('should validate Odia alpha strings', () => { + test({ + validator: 'isAlpha', + args: ['or-IN'], + valid: [ + 'ଅଆଇଈଉଊଋଌଏଐଓଔକଖଗଘଙଚଛଜଝଞଟଠଡଢଣତଥଦଧନପଫବଭମଯରଲଶଷସହଳ', + 'ଓଡ଼ିଆ', + ], + invalid: ['ଓଡ଼ିଆ123', 'ଓଡ଼ିଆ.', 'abc', ''], + }); + }); + it('should validate Bengali alpha strings', () => { + test({ + validator: 'isAlpha', + args: ['bn-IN'], + valid: [ + 'অআইঈউঊঋএঐওঔকখগঘঙচছজঝঞটঠডঢণতথদধনপফবভমযরলশষসহ', + 'বাংলা', + ], + invalid: ['বাংলা123', 'বাংলা.', 'abc', ''], + }); + }); it('should validate persian alpha strings', () => { test({ validator: 'isAlpha', @@ -2699,6 +2793,164 @@ describe('Validators', () => { ], }); }); + it('should validate Tamil alphanumeric strings', () => { + test({ + validator: 'isAlphanumeric', + args: ['ta-IN'], + valid: [ + 'தமிழ்', + 'தமிழ்123', + 'அஆஇஈ123', + 'தமிழ்123.45', + '123.45', + 'தமிழ்.', + ], + invalid: [ + 'தமிழ் ', + 'abc', + '', + ], + }); + }); + + it('should validate Telugu alphanumeric strings', () => { + test({ + validator: 'isAlphanumeric', + args: ['te-IN'], + valid: [ + 'తెలుగు', + 'తెలుగు123', + 'అఆఇఈ123', + 'తెలుగు123.45', + '123.45', + 'తెలుగు.', + ], + invalid: [ + 'abc', + '', + ], + }); + }); + + it('should validate Kannada alphanumeric strings', () => { + test({ + validator: 'isAlphanumeric', + args: ['kn-IN'], + valid: [ + 'ಕನ್ನಡ', + 'ಕನ್ನಡ123', + 'ಅಆಇಈ123', + 'ಕನ್ನಡ123.45', + '123.45', + 'ಕನ್ನಡ.', + ], + invalid: [ + 'abc', + '', + ], + }); + }); + + it('should validate Malayalam alphanumeric strings', () => { + test({ + validator: 'isAlphanumeric', + args: ['ml-IN'], + valid: [ + 'മലയാളം', + 'മലയാളം123', + 'അആഇഈ123', + 'മലയാളം123.45', + '123.45', + 'മലയാളം.', + ], + invalid: [ + 'abc', + '', + ], + }); + }); + + it('should validate Gujarati alphanumeric strings', () => { + test({ + validator: 'isAlphanumeric', + args: ['gu-IN'], + valid: [ + 'ગુજરાતી', + 'ગુજરાતી123', + 'અઆઇઈ123', + 'ગુજરાતી123.45', + '123.45', + 'ગુજરાતી.', + ], + invalid: [ + 'abc', + '', + ], + }); + }); + + it('should validate Punjabi alphanumeric strings', () => { + test({ + validator: 'isAlphanumeric', + args: ['pa-IN'], + valid: [ + 'ਪੰਜਾਬੀ', + 'ਪੰਜਾਬੀ123', + 'ਅਆਇਈ123', + 'ਪੰਜਾਬੀ123.45', + '123.45', + 'ਪੰਜਾਬੀ.', + ], + invalid: [ + 'abc', + '', + ], + }); + }); + + it('should validate Odia alphanumeric strings', () => { + test({ + validator: 'isAlphanumeric', + args: ['or-IN'], + valid: [ + 'ଓଡ଼ିଆ', + 'ଓଡ଼ିଆ123', + 'ଅଆଇଈ123', + 'ଓଡ଼ିଆ123.45', + '123.45', + 'ଓଡ଼ିଆ.', + ], + invalid: [ + 'abc', + '', + ], + }); + }); + + it('should validate Bengali alphanumeric strings', () => { + test({ + validator: 'isAlphanumeric', + args: ['bn-IN'], + valid: [ + 'বাংলা', + 'বাংলা১২৩', + 'অআইঈ১২৩', + '১২৩৪৫৬৭৮৯০', + 'বাংলা১২৩', + '১২৩৪৫', + 'বাংলা', + ], + invalid: [ + 'abc', + 'বাংলা123', + '123', + 'বাংলা ১২৩', + 'বাংলা,১২৩', + '১২৩٫৪৫', + '', + ], + }); + }); it('should error on invalid locale', () => { test({ From eee525cd117d24ac905b9432f3f5a27e96aa9719 Mon Sep 17 00:00:00 2001 From: Kevin Lentin Date: Wed, 27 Aug 2025 17:14:28 +1000 Subject: [PATCH 4/8] #2491 #2573 Simplify isBase64 to prevent stack overflow (#2574) Co-authored-by: Kevin Lentin --- src/lib/isBase64.js | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/lib/isBase64.js b/src/lib/isBase64.js index 7eb3a5b56..fd876e4c0 100644 --- a/src/lib/isBase64.js +++ b/src/lib/isBase64.js @@ -1,9 +1,9 @@ import assertString from './util/assertString'; import merge from './util/merge'; -const base64WithPadding = /^(?:[A-Za-z0-9+/]{4})*(?:[A-Za-z0-9+/]{2}==|[A-Za-z0-9+/]{3}=|[A-Za-z0-9+/]{4})$/; +const base64WithPadding = /^[A-Za-z0-9+/]+={0,2}$/; const base64WithoutPadding = /^[A-Za-z0-9+/]+$/; -const base64UrlWithPadding = /^(?:[A-Za-z0-9_-]{4})*(?:[A-Za-z0-9_-]{2}==|[A-Za-z0-9_-]{3}=|[A-Za-z0-9_-]{4})$/; +const base64UrlWithPadding = /^[A-Za-z0-9_-]+={0,2}$/; const base64UrlWithoutPadding = /^[A-Za-z0-9_-]+$/; export default function isBase64(str, options) { @@ -12,6 +12,8 @@ export default function isBase64(str, options) { if (str === '') return true; + if (options.padding && str.length % 4 !== 0) return false; + let regex; if (options.urlSafe) { regex = options.padding ? base64UrlWithPadding : base64UrlWithoutPadding; From 3c857088d58197453957a2b924dfedea328003b6 Mon Sep 17 00:00:00 2001 From: Amer Date: Wed, 27 Aug 2025 17:04:46 +0300 Subject: [PATCH 5/8] Fix: correct French VAT (FR) validation regex and add tests (#2584) --- src/lib/isVAT.js | 2 +- test/validators.test.js | 9 ++++++++- 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/src/lib/isVAT.js b/src/lib/isVAT.js index 50fcf52e0..1ec2c5991 100644 --- a/src/lib/isVAT.js +++ b/src/lib/isVAT.js @@ -60,7 +60,7 @@ export const vatMatchers = { DK: str => /^(DK)?\d{8}$/.test(str), EE: str => /^(EE)?\d{9}$/.test(str), FI: str => /^(FI)?\d{8}$/.test(str), - FR: str => /^(FR)?\w{2}\d{9}$/.test(str), + FR: str => /^(FR)([A-Z0-9]{2}\d{9})$/.test(str), DE: str => /^(DE)?\d{9}$/.test(str), EL: str => /^(EL)?\d{9}$/.test(str), HU: str => /^(HU)?\d{8}$/.test(str), diff --git a/test/validators.test.js b/test/validators.test.js index f73a48164..f474338dd 100644 --- a/test/validators.test.js +++ b/test/validators.test.js @@ -15055,11 +15055,18 @@ describe('Validators', () => { args: ['FR'], valid: [ 'FRAA123456789', - 'AA123456789', + 'FR83404833048', + 'FR40123456789', + 'FRA1123456789', + 'FR1A123456789', ], invalid: [ 'FR AA123456789', '123456789', + 'FRAA123456789A', + 'FR123456789', + 'FR 83404833048', + 'FRaa123456789', ], }); test({ From 6f436be36945e460ee624bf72a935a06daded859 Mon Sep 17 00:00:00 2001 From: Camillo Bruni Date: Wed, 3 Sep 2025 11:25:02 +0200 Subject: [PATCH 6/8] Fix typo in validators.test.js (#2581) --- test/validators.test.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/validators.test.js b/test/validators.test.js index f474338dd..12c5fc2ab 100644 --- a/test/validators.test.js +++ b/test/validators.test.js @@ -8123,7 +8123,7 @@ describe('Validators', () => { ], }, { - local: 'en-LS', + locale: 'en-LS', valid: [ '+26622123456', '+26628123456', From cbef5088f02d36caf978f378bb845fe49bdc0809 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Th=C3=A9o=20FIDRY?= <5175937+theofidry@users.noreply.github.com> Date: Tue, 21 Oct 2025 13:36:53 +0200 Subject: [PATCH 7/8] fix(isURL): improve protocol detection. Resolves CVE-2025-56200 (#2608) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Théo FIDRY <5175937+theofidry@users.noreply.github.com> Co-authored-by: manuelMarkDenver Co-authored-by: scottgigante-hubflow Co-authored-by: Henri Holopainen Co-authored-by: Rik Smale <13023439+WikiRik@users.noreply.github.com> --- .github/workflows/ci.yml | 2 +- README.md | 2 +- src/lib/isURL.js | 87 +++++++++++++++++++++++++++++++++--- test/validators.test.js | 96 ++++++++++++++++++++++++++++++++++++++++ 4 files changed, 178 insertions(+), 9 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index af090a9be..e0024eff3 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -9,7 +9,7 @@ jobs: runs-on: ubuntu-latest strategy: matrix: - node-version: [22, 20, 18, 16, 14, 12, 10, 8, 6] + node-version: [22, 20, 18, 16, 14, 12, 10, 8] name: Run tests on Node.js ${{ matrix.node-version }} steps: - name: Setup Node.js ${{ matrix.node-version }} diff --git a/README.md b/README.md index a0ea7a65d..37fbd5e3d 100644 --- a/README.md +++ b/README.md @@ -167,7 +167,7 @@ Validator | Description **isStrongPassword(str [, options])** | check if the string can be considered a strong password or not. Allows for custom requirements or scoring rules. If `returnScore` is true, then the function returns an integer score for the password rather than a boolean.
Default options:
`{ minLength: 8, minLowercase: 1, minUppercase: 1, minNumbers: 1, minSymbols: 1, returnScore: false, pointsPerUnique: 1, pointsPerRepeat: 0.5, pointsForContainingLower: 10, pointsForContainingUpper: 10, pointsForContainingNumber: 10, pointsForContainingSymbol: 10 }` **isTime(str [, options])** | check if the string is a valid time e.g. [`23:01:59`, new Date().toLocaleTimeString()].

`options` is an object which can contain the keys `hourFormat` or `mode`.

`hourFormat` is a key and defaults to `'hour24'`.

`mode` is a key and defaults to `'default'`.

`hourFormat` can contain the values `'hour12'` or `'hour24'`, `'hour24'` will validate hours in 24 format and `'hour12'` will validate hours in 12 format.

`mode` can contain the values `'default', 'withSeconds', withOptionalSeconds`, `'default'` will validate `HH:MM` format, `'withSeconds'` will validate the `HH:MM:SS` format, `'withOptionalSeconds'` will validate `'HH:MM'` and `'HH:MM:SS'` formats. **isTaxID(str, locale)** | check if the string is a valid Tax Identification Number. Default locale is `en-US`.

More info about exact TIN support can be found in `src/lib/isTaxID.js`.

Supported locales: `[ 'bg-BG', 'cs-CZ', 'de-AT', 'de-DE', 'dk-DK', 'el-CY', 'el-GR', 'en-CA', 'en-GB', 'en-IE', 'en-US', 'es-AR', 'es-ES', 'et-EE', 'fi-FI', 'fr-BE', 'fr-CA', 'fr-FR', 'fr-LU', 'hr-HR', 'hu-HU', 'it-IT', 'lb-LU', 'lt-LT', 'lv-LV', 'mt-MT', 'nl-BE', 'nl-NL', 'pl-PL', 'pt-BR', 'pt-PT', 'ro-RO', 'sk-SK', 'sl-SI', 'sv-SE', 'uk-UA']`. -**isURL(str [, options])** | check if the string is a URL.

`options` is an object which defaults to `{ protocols: ['http','https','ftp'], require_tld: true, require_protocol: false, require_host: true, require_port: false, require_valid_protocol: true, allow_underscores: false, host_whitelist: false, host_blacklist: false, allow_trailing_dot: false, allow_protocol_relative_urls: false, allow_fragments: true, allow_query_components: true, disallow_auth: false, validate_length: true }`.

`protocols` - valid protocols can be modified with this option.
`require_tld` - If set to false isURL will not check if the URL's host includes a top-level domain.
`require_protocol` - if set to true isURL will return false if protocol is not present in the URL.
`require_host` - if set to false isURL will not check if host is present in the URL.
`require_port` - if set to true isURL will check if port is present in the URL.
`require_valid_protocol` - isURL will check if the URL's protocol is present in the protocols option.
`allow_underscores` - if set to true, the validator will allow underscores in the URL.
`host_whitelist` - if set to an array of strings or regexp, and the domain matches none of the strings defined in it, the validation fails.
`host_blacklist` - if set to an array of strings or regexp, and the domain matches any of the strings defined in it, the validation fails.
`allow_trailing_dot` - if set to true, the validator will allow the domain to end with a `.` character.
`allow_protocol_relative_urls` - if set to true protocol relative URLs will be allowed.
`allow_fragments` - if set to false isURL will return false if fragments are present.
`allow_query_components` - if set to false isURL will return false if query components are present.
`disallow_auth` - if set to true, the validator will fail if the URL contains an authentication component, e.g. `http://username:password@example.com`.
`validate_length` - if set to false isURL will skip string length validation. `max_allowed_length` will be ignored if this is set as `false`.
`max_allowed_length` - if set, isURL will not allow URLs longer than the specified value (default is 2084 that IE maximum URL length).
+**isURL(str [, options])** | check if the string is a URL.

`options` is an object which defaults to `{ protocols: ['http','https','ftp'], require_tld: true, require_protocol: false, require_host: true, require_port: false, require_valid_protocol: true, allow_underscores: false, host_whitelist: false, host_blacklist: false, allow_trailing_dot: false, allow_protocol_relative_urls: false, allow_fragments: true, allow_query_components: true, disallow_auth: false, validate_length: true }`.

`protocols` - valid protocols can be modified with this option.
`require_tld` - If set to false isURL will not check if the URL's host includes a top-level domain.
`require_protocol` - **RECOMMENDED** if set to true isURL will return false if protocol is not present in the URL. Without this setting, some malicious URLs cannot be distinguishable from a valid URL with authentication information.
`require_host` - if set to false isURL will not check if host is present in the URL.
`require_port` - if set to true isURL will check if port is present in the URL.
`require_valid_protocol` - isURL will check if the URL's protocol is present in the protocols option.
`allow_underscores` - if set to true, the validator will allow underscores in the URL.
`host_whitelist` - if set to an array of strings or regexp, and the domain matches none of the strings defined in it, the validation fails.
`host_blacklist` - if set to an array of strings or regexp, and the domain matches any of the strings defined in it, the validation fails.
`allow_trailing_dot` - if set to true, the validator will allow the domain to end with a `.` character.
`allow_protocol_relative_urls` - if set to true protocol relative URLs will be allowed.
`allow_fragments` - if set to false isURL will return false if fragments are present.
`allow_query_components` - if set to false isURL will return false if query components are present.
`disallow_auth` - if set to true, the validator will fail if the URL contains an authentication component, e.g. `http://username:password@example.com`.
`validate_length` - if set to false isURL will skip string length validation. `max_allowed_length` will be ignored if this is set as `false`.
`max_allowed_length` - if set, isURL will not allow URLs longer than the specified value (default is 2084 that IE maximum URL length).
**isULID(str)** | check if the string is a [ULID](https://github.com/ulid/spec). **isUUID(str [, version])** | check if the string is an RFC9562 UUID.
`version` is one of `'1'`-`'8'`, `'nil'`, `'max'`, `'all'` or `'loose'`. The `'loose'` option checks if the string is a UUID-like string with hexadecimal values, ignoring RFC9565. **isVariableWidth(str)** | check if the string contains a mixture of full and half-width chars. diff --git a/src/lib/isURL.js b/src/lib/isURL.js index 0fec384ba..8ae971ed6 100644 --- a/src/lib/isURL.js +++ b/src/lib/isURL.js @@ -83,21 +83,94 @@ export default function isURL(url, options) { split = url.split('?'); url = split.shift(); - split = url.split('://'); - if (split.length > 1) { - protocol = split.shift().toLowerCase(); + // Replaced the 'split("://")' logic with a regex to match the protocol. + // This correctly identifies schemes like `javascript:` which don't use `//`. + // However, we need to be careful not to confuse authentication credentials (user:password@host) + // with protocols. A colon before an @ symbol might be part of auth, not a protocol separator. + const protocol_match = url.match(/^([a-z][a-z0-9+\-.]*):/i); + let had_explicit_protocol = false; + + const cleanUpProtocol = (potential_protocol) => { + had_explicit_protocol = true; + protocol = potential_protocol.toLowerCase(); + if (options.require_valid_protocol && options.protocols.indexOf(protocol) === -1) { + // The identified protocol is not in the allowed list. return false; } + + // Remove the protocol from the URL string. + return url.substring(protocol_match[0].length); + }; + + if (protocol_match) { + const potential_protocol = protocol_match[1]; + const after_colon = url.substring(protocol_match[0].length); + + // Check if what follows looks like authentication credentials (user:password@host) + // rather than a protocol. This happens when: + // 1. There's no `//` after the colon (protocols like `http://` have this) + // 2. There's an `@` symbol before any `/` + // 3. The part before `@` contains only valid auth characters (alphanumeric, -, _, ., %, :) + const starts_with_slashes = after_colon.slice(0, 2) === '//'; + + if (!starts_with_slashes) { + const first_slash_position = after_colon.indexOf('/'); + const before_slash = first_slash_position === -1 + ? after_colon + : after_colon.substring(0, first_slash_position); + const at_position = before_slash.indexOf('@'); + + if (at_position !== -1) { + const before_at = before_slash.substring(0, at_position); + const valid_auth_regex = /^[a-zA-Z0-9\-_.%:]*$/; + const is_valid_auth = valid_auth_regex.test(before_at); + + if (is_valid_auth) { + // This looks like authentication (e.g., user:password@host), not a protocol + if (options.require_protocol) { + return false; + } + + // Don't consume the colon; let the auth parsing handle it later + } else { + // This looks like a malicious protocol (e.g., javascript:alert();@host) + url = cleanUpProtocol(potential_protocol); + + if (url === false) { + return false; + } + } + } else { + // No @ symbol, this is definitely a protocol + url = cleanUpProtocol(potential_protocol); + + if (url === false) { + return false; + } + } + } else { + // Starts with '//', this is definitely a protocol like http:// + url = cleanUpProtocol(potential_protocol); + + if (url === false) { + return false; + } + } } else if (options.require_protocol) { return false; - } else if (url.slice(0, 2) === '//') { - if (!options.allow_protocol_relative_urls) { + } + + // Handle leading '//' only as protocol-relative when there was NO explicit protocol. + // If there was an explicit protocol, '//' is the normal separator + // and should be stripped unconditionally. + if (url.slice(0, 2) === '//') { + if (!had_explicit_protocol && !options.allow_protocol_relative_urls) { return false; } - split[0] = url.slice(2); + + url = url.slice(2); } - url = split.join('://'); if (url === '') { return false; diff --git a/test/validators.test.js b/test/validators.test.js index 12c5fc2ab..a3c5f5a5d 100644 --- a/test/validators.test.js +++ b/test/validators.test.js @@ -424,6 +424,12 @@ describe('Validators', () => { 'http://[2010:836B:4179::836B:4179]', 'http://example.com/example.json#/foo/bar', 'http://1337.com', + // TODO: those probably should not be marked as valid URLs; CVE-2025-56200 + /* eslint-disable no-script-url */ + 'javascript:%61%6c%65%72%74%28%31%29@example.com', + 'http://evil-site.com@example.com/', + 'javascript:alert(1)@example.com', + /* eslint-enable no-script-url */ ], invalid: [ 'http://localhost:3000/', @@ -466,6 +472,18 @@ describe('Validators', () => { '////foobar.com', 'http:////foobar.com', 'https://example.com/foo//', + // the following tests are because of CVE-2025-56200 + /* eslint-disable no-script-url */ + "javascript:alert(1);a=';@example.com/alert(1)'", + 'JaVaScRiPt:alert(1)@example.com', + 'javascript:/* comment */alert(1)@example.com', + 'javascript:var a=1; alert(a);@example.com', + 'javascript:alert(1)@user@example.com', + 'javascript:alert(1)@example.com?q=safe', + 'data:text/html,@example.com', + 'vbscript:msgbox("XSS")@example.com', + '//evil-site.com/path@example.com', + /* eslint-enable no-script-url */ ], }); }); @@ -478,9 +496,11 @@ describe('Validators', () => { }], valid: [ 'rtmp://foobar.com', + 'rtmp:foobar.com', ], invalid: [ 'http://foobar.com', + 'tel:+15551234567', ], }); }); @@ -533,6 +553,9 @@ describe('Validators', () => { 'rtmp://foobar.com', 'http://foobar.com', 'test://foobar.com', + // Dangerous! This allows to mark malicious URLs as a valid URL (CVE-2025-56200) + // eslint-disable-next-line no-script-url + 'javascript:alert(1);@example.com', ], invalid: [ 'mailto:test@example.com', @@ -704,6 +727,61 @@ describe('Validators', () => { }); }); + it('should validate authentication strings if a protocol is not required', () => { + test({ + validator: 'isURL', + args: [{ + require_protocol: false, + }], + valid: [ + 'user:pw@foobar.com/', + ], + invalid: [ + 'user:pw,@foobar.com/', + ], + }); + }); + + it('should reject authentication strings if a protocol is required', () => { + test({ + validator: 'isURL', + args: [{ + require_protocol: true, + }], + valid: [ + 'http://user:pw@foobar.com/', + 'https://user:password@example.com', + 'ftp://admin:pass@ftp.example.com/', + ], + invalid: [ + 'user:pw@foobar.com/', + 'user:password@example.com', + 'admin:pass@ftp.example.com/', + ], + }); + }); + + it('should reject invalid protocols when require_valid_protocol is enabled', () => { + test({ + validator: 'isURL', + args: [{ + require_valid_protocol: true, + protocols: ['http', 'https', 'ftp'], + }], + valid: [ + 'http://example.com', + 'https://example.com', + 'ftp://example.com', + ], + invalid: [ + // eslint-disable-next-line no-script-url + 'javascript:alert(1);@example.com', + 'data:text/html,@example.com', + 'file:///etc/passwd@example.com', + ], + }); + }); + it('should let users specify a host whitelist', () => { test({ validator: 'isURL', @@ -782,6 +860,24 @@ describe('Validators', () => { }); }); + it('GHSA-9965-vmph-33xx vulnerability - protocol delimiter parsing difference', () => { + const DOMAIN_WHITELIST = ['example.com']; + + test({ + validator: 'isURL', + args: [{ + protocols: ['https'], + host_whitelist: DOMAIN_WHITELIST, + require_host: false, + }], + valid: [], + invalid: [ + // eslint-disable-next-line no-script-url + "javascript:alert(1);a=';@example.com/alert(1)", + ], + }); + }); + it('should allow rejecting urls containing authentication information', () => { test({ validator: 'isURL', From 30d4fe02c16d36ed471f12da658c4b5d843781e0 Mon Sep 17 00:00:00 2001 From: Anthony Nandaa Date: Sun, 26 Oct 2025 07:41:17 +0300 Subject: [PATCH 8/8] 13.15.20 --- CHANGELOG.md | 13 +++++++++++++ package.json | 2 +- src/index.js | 2 +- 3 files changed, 15 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 549377fd2..25192b24a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,16 @@ +# 13.15.20 + +### Fixes, New Locales and Enhancements + +- [#2556](https://github.com/validatorjs/validator.js/pull/2556) `isMobilePhone`: add `ar-QA` locale @WardKhaddour +- [#2576](https://github.com/validatorjs/validator.js/pull/2576) `isAlpha`/`isAlphanuneric`: add Indic locales (`ta-IN`, `te-IN`, `kn-IN`, `ml-IN`, `gu-IN`, `pa-IN`, `or-IN`) @avadootharajesh +- [#2574](https://github.com/validatorjs/validator.js/pull/2574) `isBase64`: improve padding regex @KrayzeeKev +- [#2584](https://github.com/validatorjs/validator.js/pull/2584) `isVAT`: improve `FR` locale @iamAmer +- [#2608](https://github.com/validatorjs/validator.js/pull/2608) `isURL`: improve protocol detection. Resolves CVE-2025-56200 @theofidry +- **Doc fixes and others:** + - [#2563](https://github.com/validatorjs/validator.js/pull/2563) @stoneLeaf + - [#2581](https://github.com/validatorjs/validator.js/pull/2581) @camillobruni + # 13.15.15 ### Fixes, New Locales and Enhancements diff --git a/package.json b/package.json index 7e84ef71d..106694955 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "validator", "description": "String validation and sanitization", - "version": "13.15.15", + "version": "13.15.20", "sideEffects": false, "homepage": "https://github.com/validatorjs/validator.js", "files": [ diff --git a/src/index.js b/src/index.js index 87be7113c..b69c43649 100644 --- a/src/index.js +++ b/src/index.js @@ -130,7 +130,7 @@ import isStrongPassword from './lib/isStrongPassword'; import isVAT from './lib/isVAT'; -const version = '13.15.15'; +const version = '13.15.20'; const validator = { version,