From 4374e2eb529eefe3317853f6b61a36a8cc474457 Mon Sep 17 00:00:00 2001 From: xxiaoxiong Date: Thu, 28 May 2026 09:45:23 +0800 Subject: [PATCH 1/7] fix(isJWT): reject tokens with non-JSON header or payload The isJWT function currently only validates that the JWT has three dot-separated segments and that each segment is valid URL-safe base64. However, it does not verify that the decoded header and payload are valid JSON. This allows strings like `foo.bar.dGVzdA` to pass validation even though they are not valid JWTs. This fix decodes the base64-encoded header and payload and checks that they parse as valid JSON before returning true. Fixes #2511 --- src/lib/isJWT.js | 29 ++++++++++++++++++++++++++++- 1 file changed, 28 insertions(+), 1 deletion(-) diff --git a/src/lib/isJWT.js b/src/lib/isJWT.js index 1d0ade5ee..6af92e597 100644 --- a/src/lib/isJWT.js +++ b/src/lib/isJWT.js @@ -1,6 +1,24 @@ import assertString from './util/assertString'; import isBase64 from './isBase64'; +function isBase64EncodedJSON(base64Str) { + // Convert URL-safe base64 to standard base64 + const standardBase64 = base64Str.replace(/-/g, '+').replace(/_/g, '/'); + try { + const decoded = typeof globalThis.atob === 'function' + ? globalThis.atob(standardBase64) + : Buffer.from(standardBase64, 'base64').toString('binary'); + try { + JSON.parse(decoded); + return true; + } catch { + return false; + } + } catch { + return false; + } +} + export default function isJWT(str) { assertString(str); @@ -11,5 +29,14 @@ export default function isJWT(str) { return false; } - return dotSplit.reduce((acc, currElem) => acc && isBase64(currElem, { urlSafe: true }), true); + const [header, payload, signature] = dotSplit; + + if (!isBase64(header, { urlSafe: true }) || + !isBase64(payload, { urlSafe: true }) || + !isBase64(signature, { urlSafe: true })) { + return false; + } + + // header and payload must be valid JSON when decoded + return isBase64EncodedJSON(header) && isBase64EncodedJSON(payload); } From cd3888b86f557db82c8b326a7656cc32dbf9de3f Mon Sep 17 00:00:00 2001 From: xxiaoxiong Date: Thu, 28 May 2026 09:51:54 +0800 Subject: [PATCH 2/7] fix(isJWT): use catch parameter to comply with ESLint ecmaVersion 6 --- src/lib/isJWT.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/lib/isJWT.js b/src/lib/isJWT.js index 6af92e597..c750cbf14 100644 --- a/src/lib/isJWT.js +++ b/src/lib/isJWT.js @@ -11,10 +11,10 @@ function isBase64EncodedJSON(base64Str) { try { JSON.parse(decoded); return true; - } catch { + } catch (_err) { return false; } - } catch { + } catch (_err) { return false; } } From b27aef3621889afe15da173f54ccd14d64867603 Mon Sep 17 00:00:00 2001 From: xxiaoxiong Date: Thu, 28 May 2026 10:32:42 +0800 Subject: [PATCH 3/7] fix: replace globalThis with cross-env getGlobalScope helper --- src/lib/isJWT.js | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/src/lib/isJWT.js b/src/lib/isJWT.js index c750cbf14..c2913d3c8 100644 --- a/src/lib/isJWT.js +++ b/src/lib/isJWT.js @@ -1,12 +1,21 @@ import assertString from './util/assertString'; import isBase64 from './isBase64'; +function getGlobalScope() { + if (typeof globalThis !== 'undefined') return globalThis; + if (typeof self !== 'undefined') return self; + if (typeof window !== 'undefined') return window; + if (typeof global !== 'undefined') return global; + return {}; +} + function isBase64EncodedJSON(base64Str) { // Convert URL-safe base64 to standard base64 const standardBase64 = base64Str.replace(/-/g, '+').replace(/_/g, '/'); try { - const decoded = typeof globalThis.atob === 'function' - ? globalThis.atob(standardBase64) + const scope = getGlobalScope(); + const decoded = typeof scope.atob === 'function' + ? scope.atob(standardBase64) : Buffer.from(standardBase64, 'base64').toString('binary'); try { JSON.parse(decoded); From acc66aee55f69c0c15e6f63825117573356641fd Mon Sep 17 00:00:00 2001 From: xxiaoxiong Date: Thu, 28 May 2026 10:54:24 +0800 Subject: [PATCH 4/7] fix: avoid all ES6+ syntax for Node 0.10 compat --- src/lib/isJWT.js | 28 +++++++++++++--------------- 1 file changed, 13 insertions(+), 15 deletions(-) diff --git a/src/lib/isJWT.js b/src/lib/isJWT.js index c2913d3c8..684ca497f 100644 --- a/src/lib/isJWT.js +++ b/src/lib/isJWT.js @@ -1,29 +1,26 @@ import assertString from './util/assertString'; import isBase64 from './isBase64'; -function getGlobalScope() { - if (typeof globalThis !== 'undefined') return globalThis; +var getGlobal = function() { + if (typeof global !== 'undefined') return global; if (typeof self !== 'undefined') return self; if (typeof window !== 'undefined') return window; - if (typeof global !== 'undefined') return global; return {}; -} +}(); function isBase64EncodedJSON(base64Str) { - // Convert URL-safe base64 to standard base64 - const standardBase64 = base64Str.replace(/-/g, '+').replace(/_/g, '/'); + var standardBase64 = base64Str.replace(/-/g, '+').replace(/_/g, '/'); try { - const scope = getGlobalScope(); - const decoded = typeof scope.atob === 'function' - ? scope.atob(standardBase64) + var decoded = typeof getGlobal.atob === 'function' + ? getGlobal.atob(standardBase64) : Buffer.from(standardBase64, 'base64').toString('binary'); try { JSON.parse(decoded); return true; - } catch (_err) { + } catch (e2) { return false; } - } catch (_err) { + } catch (e) { return false; } } @@ -31,14 +28,16 @@ function isBase64EncodedJSON(base64Str) { export default function isJWT(str) { assertString(str); - const dotSplit = str.split('.'); - const len = dotSplit.length; + var dotSplit = str.split('.'); + var len = dotSplit.length; if (len !== 3) { return false; } - const [header, payload, signature] = dotSplit; + var header = dotSplit[0]; + var payload = dotSplit[1]; + var signature = dotSplit[2]; if (!isBase64(header, { urlSafe: true }) || !isBase64(payload, { urlSafe: true }) || @@ -46,6 +45,5 @@ export default function isJWT(str) { return false; } - // header and payload must be valid JSON when decoded return isBase64EncodedJSON(header) && isBase64EncodedJSON(payload); } From 1b8691301c52be89fa2d56c333c60e75f5fd5314 Mon Sep 17 00:00:00 2001 From: xxiaoxiong Date: Thu, 28 May 2026 11:05:43 +0800 Subject: [PATCH 5/7] fix: use getGlobalScope helper instead of var to comply with ESLint --- src/lib/isJWT.js | 27 ++++++++++++++------------- 1 file changed, 14 insertions(+), 13 deletions(-) diff --git a/src/lib/isJWT.js b/src/lib/isJWT.js index 684ca497f..dd9eeeae1 100644 --- a/src/lib/isJWT.js +++ b/src/lib/isJWT.js @@ -1,18 +1,20 @@ import assertString from './util/assertString'; import isBase64 from './isBase64'; -var getGlobal = function() { +function getGlobalScope() { if (typeof global !== 'undefined') return global; if (typeof self !== 'undefined') return self; if (typeof window !== 'undefined') return window; return {}; -}(); +} function isBase64EncodedJSON(base64Str) { - var standardBase64 = base64Str.replace(/-/g, '+').replace(/_/g, '/'); + // Convert URL-safe base64 to standard base64 + const standardBase64 = base64Str.replace(/-/g, '+').replace(/_/g, '/'); try { - var decoded = typeof getGlobal.atob === 'function' - ? getGlobal.atob(standardBase64) + const scope = getGlobalScope(); + const decoded = typeof scope.atob === 'function' + ? scope.atob(standardBase64) : Buffer.from(standardBase64, 'base64').toString('binary'); try { JSON.parse(decoded); @@ -28,22 +30,21 @@ function isBase64EncodedJSON(base64Str) { export default function isJWT(str) { assertString(str); - var dotSplit = str.split('.'); - var len = dotSplit.length; + const dotSplit = str.split('.'); + const len = dotSplit.length; if (len !== 3) { return false; } - var header = dotSplit[0]; - var payload = dotSplit[1]; - var signature = dotSplit[2]; + const [header, payload, signature] = dotSplit; - if (!isBase64(header, { urlSafe: true }) || - !isBase64(payload, { urlSafe: true }) || - !isBase64(signature, { urlSafe: true })) { + if (!isBase64(header, { urlSafe: true }) + || !isBase64(payload, { urlSafe: true }) + || !isBase64(signature, { urlSafe: true })) { return false; } + // header and payload must be valid JSON when decoded return isBase64EncodedJSON(header) && isBase64EncodedJSON(payload); } From 6ce349bc9d7b7f360cbf1c861b8f9b890ed64769 Mon Sep 17 00:00:00 2001 From: xxiaoxiong Date: Thu, 28 May 2026 11:41:38 +0800 Subject: [PATCH 6/7] test: add non-JSON header/payload test cases for isJWT --- test/validators.test.js | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/test/validators.test.js b/test/validators.test.js index a4c3d7193..afa5b20fd 100644 --- a/test/validators.test.js +++ b/test/validators.test.js @@ -5549,6 +5549,10 @@ describe('Validators', () => { 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NSIsIm5hbWUiOiJKb2huIERvZSIsImlhdCI6MTYxNjY1Mzg3Mn0.eyJpc3MiOiJodHRwczovL2V4YW1wbGUuY29tIiwiaWF0IjoxNjE2NjUzODcyLCJleHAiOjE2MTY2NTM4ODJ9.a1jLRQkO5TV5y5ERcaPAiM9Xm2gBdRjKrrCpHkGr_8M', '$Zs.ewu.su84', 'ks64$S/9.dy$§kz.3sd73b', + // non-JSON header (valid base64 URL-safe but not valid JSON when decoded) + 'ZmFrZSBoZWFkZXI.eyJzdWIiOiIxMjM0NTY3ODkwIn0.ZmFrZXNpZw', + // non-JSON payload (valid base64 URL-safe but not valid JSON) + 'eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.bm9uLWpzb24tcGF5bG9hZA.sig', ], error: [ [], From a9433ef378fe03f514feb7de8f27e7cfb3f7702e Mon Sep 17 00:00:00 2001 From: xxiaoxiong Date: Thu, 28 May 2026 21:48:27 +0800 Subject: [PATCH 7/7] fix(isMobilePhone): add missing Uzbekistan carrier codes 33, 55, and 77 The `isMobilePhone` function for `uz-UZ` locale was missing newer carrier codes 33 (Humans), 55, and 77 (Ucell). A valid number like `+998770178734` was incorrectly rejected. This adds the missing prefixes to the regex pattern and includes corresponding test cases. Closes #2683 --- src/lib/isMobilePhone.js | 2 +- test/validators.test.js | 5 +++++ 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/src/lib/isMobilePhone.js b/src/lib/isMobilePhone.js index 32379bde5..5008353ff 100644 --- a/src/lib/isMobilePhone.js +++ b/src/lib/isMobilePhone.js @@ -154,7 +154,7 @@ const phones = { 'tr-TR': /^(\+?90|0)?5\d{9}$/, 'tk-TM': /^(\+993|993|8)\d{8}$/, 'uk-UA': /^(\+?38)?0(50|6[36-8]|7[357]|9[1-9])\d{7}$/, - 'uz-UZ': /^(\+?998)?(6[125-79]|7[1-69]|88|9\d)\d{7}$/, + 'uz-UZ': /^(\+?998)?(33|55|6[125-79]|7[1-69]|77|88|9\d)\d{7}$/, 'vi-VN': /^((\+?84)|0)((3([2-9]))|(5([25689]))|(7([0|6-9]))|(8([1-9]))|(9([0-9])))([0-9]{7})$/, 'zh-CN': /^((\+|00)86)?(1[3-9]|9[28])\d{9}$/, 'zh-TW': /^(\+?886\-?|0)?9\d{8}$/, diff --git a/test/validators.test.js b/test/validators.test.js index afa5b20fd..50e70a6f6 100644 --- a/test/validators.test.js +++ b/test/validators.test.js @@ -10436,6 +10436,11 @@ describe('Validators', () => { '+998957124555', '998957124555', '957124555', + '+998770178734', + '998771234567', + '771234567', + '+998550123456', + '+998330123456', ], invalid: [ '+998644835244',