VYPR
Medium severity5.3NVD Advisory· Published Apr 9, 2026· Updated Apr 17, 2026

CVE-2026-35040

CVE-2026-35040

Description

fast-jwt provides fast JSON Web Token (JWT) implementation. Prior to 6.2.1, using certain modifiers on RegExp objects in the allowedAud, allowedIss, allowedSub, allowedJti, or allowedNonce options in verify functions can cause certain unintended behaviours. This is because some modifiers are stateful and will cause failures in every second verification attempt regardless of the validity of the token provided. Such modifiers are /g (global matching) and /y (sticky matching). This does NOT allow invalid tokens to be accepted, only for valid tokens to be improperly rejected in some configurations. Instead it causes 50% of valid authentication requests to fail in an alternating pattern. This vulnerability is fixed in 6.2.1.

Affected packages

Versions sourced from the GitHub Security Advisory.

PackageAffected versionsPatched versions
fast-jwtnpm
< 6.2.16.2.1

Affected products

1

Patches

1
18d25904e461

Fix/regex non deterministic validation (#593)

https://github.com/nearform/fast-jwtAntonio AttanasioApr 9, 2026via ghsa
2 files changed · +87 1
  • src/verifier.js+13 1 modified
    @@ -44,6 +44,15 @@ function ensureStringClaimMatcher(raw) {
       return raw
         .filter(r => r)
         .map(r => {
    +      if (r instanceof RegExp) {
    +        return {
    +          test: v => {
    +            r.lastIndex = 0
    +            return r.test(v)
    +          }
    +        }
    +      }
    +
           if (r && typeof r.test === 'function') {
             return r
           }
    @@ -510,7 +519,10 @@ module.exports = function createVerifier(options) {
         throw new TokenError(TokenError.codes.invalidOption, 'The requiredClaims option must be an array.')
       }
     
    -  if (allowedCritHeaders !== undefined && !Array.isArray(allowedCritHeaders)) {
    +  if (
    +    allowedCritHeaders !== undefined &&
    +    (!Array.isArray(allowedCritHeaders) || allowedCritHeaders.some(h => typeof h !== 'string' || h.length === 0))
    +  ) {
         throw new TokenError(TokenError.codes.invalidOption, 'The allowedCritHeaders option must be an array of strings.')
       }
     
    
  • test/verifier.spec.js+74 0 modified
    @@ -1778,6 +1778,73 @@ test('default errorCacheTTL should not cache errors when sub millisecond executi
       t.mock.timers.reset()
     })
     
    +test('stateful RegExp /g flag must not cause non-deterministic claim validation - allowedAud', t => {
    +  t.mock.timers.enable({ now: 100000 })
    +  const sign = createSigner({ key: 'secret' })
    +  const token = sign({ aud: 'admin', iss: 'issuer', sub: 'subject', jti: 'id-123', nonce: 'nonce-xyz' })
    +  const verifier = createVerifier({ key: 'secret', allowedAud: /^admin$/g })
    +
    +  // All 8 successive calls with the same valid token must succeed
    +  for (let i = 0; i < 8; i++) {
    +    t.assert.doesNotThrow(() => verifier(token), `call ${i} should pass with /g flag on allowedAud`)
    +  }
    +})
    +
    +test('stateful RegExp /y flag must not cause non-deterministic claim validation - allowedAud', t => {
    +  t.mock.timers.enable({ now: 100000 })
    +  const sign = createSigner({ key: 'secret' })
    +  const token = sign({ aud: 'admin', iss: 'issuer', sub: 'subject', jti: 'id-123', nonce: 'nonce-xyz' })
    +  const verifier = createVerifier({ key: 'secret', allowedAud: /^admin$/y })
    +
    +  for (let i = 0; i < 8; i++) {
    +    t.assert.doesNotThrow(() => verifier(token), `call ${i} should pass with /y flag on allowedAud`)
    +  }
    +})
    +
    +test('stateful RegExp /g flag must not cause non-deterministic claim validation - allowedIss', t => {
    +  t.mock.timers.enable({ now: 100000 })
    +  const sign = createSigner({ key: 'secret' })
    +  const token = sign({ aud: 'admin', iss: 'issuer', sub: 'subject', jti: 'id-123', nonce: 'nonce-xyz' })
    +  const verifier = createVerifier({ key: 'secret', allowedIss: /^issuer$/g })
    +
    +  for (let i = 0; i < 8; i++) {
    +    t.assert.doesNotThrow(() => verifier(token), `call ${i} should pass with /g flag on allowedIss`)
    +  }
    +})
    +
    +test('stateful RegExp /g flag must not cause non-deterministic claim validation - allowedSub', t => {
    +  t.mock.timers.enable({ now: 100000 })
    +  const sign = createSigner({ key: 'secret' })
    +  const token = sign({ aud: 'admin', iss: 'issuer', sub: 'subject', jti: 'id-123', nonce: 'nonce-xyz' })
    +  const verifier = createVerifier({ key: 'secret', allowedSub: /^subject$/g })
    +
    +  for (let i = 0; i < 8; i++) {
    +    t.assert.doesNotThrow(() => verifier(token), `call ${i} should pass with /g flag on allowedSub`)
    +  }
    +})
    +
    +test('stateful RegExp /g flag must not cause non-deterministic claim validation - allowedJti', t => {
    +  t.mock.timers.enable({ now: 100000 })
    +  const sign = createSigner({ key: 'secret' })
    +  const token = sign({ aud: 'admin', iss: 'issuer', sub: 'subject', jti: 'id-123', nonce: 'nonce-xyz' })
    +  const verifier = createVerifier({ key: 'secret', allowedJti: /^id-123$/g })
    +
    +  for (let i = 0; i < 8; i++) {
    +    t.assert.doesNotThrow(() => verifier(token), `call ${i} should pass with /g flag on allowedJti`)
    +  }
    +})
    +
    +test('stateful RegExp /g flag must not cause non-deterministic claim validation - allowedNonce', t => {
    +  t.mock.timers.enable({ now: 100000 })
    +  const sign = createSigner({ key: 'secret' })
    +  const token = sign({ aud: 'admin', iss: 'issuer', sub: 'subject', jti: 'id-123', nonce: 'nonce-xyz' })
    +  const verifier = createVerifier({ key: 'secret', allowedNonce: /^nonce-xyz$/g })
    +
    +  for (let i = 0; i < 8; i++) {
    +    t.assert.doesNotThrow(() => verifier(token), `call ${i} should pass with /g flag on allowedNonce`)
    +  }
    +})
    +
     // --- crit header validation (RFC 7515 §4.1.11) ---
     
     test('crit: rejects token with unknown critical extension (secure-by-default, no allowedCritHeaders)', t => {
    @@ -1874,3 +1941,10 @@ test('crit: throws on invalid allowedCritHeaders option (not an array)', t => {
         message: 'The allowedCritHeaders option must be an array of strings.'
       })
     })
    +
    +test('crit: throws on invalid allowedCritHeaders option (empty string)', t => {
    +  t.assert.throws(() => createVerifier({ key: 'secret', allowedCritHeaders: [''] }), {
    +    code: 'FAST_JWT_INVALID_OPTION',
    +    message: 'The allowedCritHeaders option must be an array of strings.'
    +  })
    +})
    

Vulnerability mechanics

Generated by null/stub on May 9, 2026. Inputs: CWE entries + fix-commit diffs from this CVE's patches. Citations validated against bundle.

References

6

News mentions

0

No linked articles in our index yet.