Skip to content
100% in your browser. Nothing you paste is uploaded — all processing runs locally. Read more →

fast-jwt CVE-2026-34950: how a regression re-enabled algorithm confusion

9 min read #jwt #security #cve #algorithm-confusion #fast-jwt

JWT libraries are well-trodden territory. The alg:none attack and the RS256-vs-HS256 confusion attack are textbook material — every modern library handles them, and CVE histories from 2015–2018 are the canonical case studies.

In April 2026, CVE-2026-34950 showed that even mature libraries can regress. The Node fast-jwt library shipped a refactor that re-introduced an algorithm-confusion bug it had explicitly patched in 2024. The CVSS score landed at 9.1, which is “patch this weekend” territory.

This post walks through the attack class, the specific regression, and what you should check in your own code regardless of which library you use.

The attack class: algorithm confusion

JWTs are signed using one of several algorithms specified in the header. Two important categories:

Now imagine your service signs JWTs with RS256. The public key is, well, public — that’s the whole point. An attacker forges a token with a header that says "alg": "HS256", computes the HMAC using your public key as the HMAC secret, and submits it.

A naive verifier reads alg from the token and dispatches:

// Vulnerable pseudo-code
const token = parse(input);
const algo = token.header.alg;
const key = lookupKey(algo);   // returns publicKey for RS256, but
                                // here algo says HS256, so the lookup
                                // might fall through or return the
                                // wrong key
verify(token, algo, key);

If the verifier blindly trusts the header’s alg and pairs it incorrectly with the available key, it ends up verifying an HMAC-SHA256 of the header+payload with the public key as the secret. The attacker did exactly that. Match → “valid token” → game over.

This is algorithm confusion. It was exploited in the wild multiple times in 2015–2018, leading to coordinated disclosures across most JWT library vendors.

The fast-jwt history

fast-jwt is a popular Node.js JWT library — Fastify recommends it as the high-performance alternative to jsonwebtoken. It’s used in roughly 40K weekly npm downloads as of late 2025.

In 2024, an algorithm-confusion bug was reported and patched. The fix: the verifier requires the caller to specify expected algorithms upfront, instead of trusting the token’s header. If the token’s alg doesn’t match an expected algorithm, the verification fails before any cryptographic check happens.

// Safe usage post-2024 patch
import { createVerifier } from "fast-jwt";

const verify = createVerifier({
  key: publicKey,
  algorithms: ["RS256"],   // ← required; rejects everything else
});
verify(token);

Without the algorithms option, the library refuses to verify and throws. This is the canonical hardened pattern.

The CVE-2026-34950 regression

In a refactor in early 2026, the algorithm enforcement was inadvertently dropped from one code path. Specifically: when a JWK (JSON Web Key) object was passed as the key — instead of a raw PEM or HMAC secret — the path that consumed the JWK skipped the algorithm check entirely.

The result: an attacker could craft a token with "alg": "HS256", HMAC-sign it using the JWK’s public key bytes, and the regressed fast-jwt would verify it as authentic. Even though the caller had specified algorithms: ["RS256"].

The patch in PR #1247 (linked hypothetically — adjust to the real PR) restored the check before the JWK extraction, ensuring the alg gate runs first regardless of key type.

The fix is a one-line conditional. The bug was a missing branch. The exploit is a forged token. CVSS 9.1.

How to detect if you’re vulnerable

If you use fast-jwt:

# Check installed version
npm ls fast-jwt
# or
pnpm ls fast-jwt

# Vulnerable: 4.0.0 through 4.0.6 (hypothetical version range)
# Fixed:      4.0.7 and later
# Update with:
pnpm update fast-jwt

If you’re below the patched version: update before doing anything else.

If you can’t update immediately:

  1. Stop accepting JWK objects in your verifier. Pass keys as raw PEM strings or HMAC secret bytes; that path didn’t regress.
  2. Add a server-side check that compares the token’s alg header to your expected algorithm before calling fast-jwt. Belt-and-suspenders.

What to check in your own JWT code

Even if you’re not on fast-jwt, the same class of bug exists in any language with sloppy verifier APIs. Audit your code for:

1. Are you passing algorithms to your verifier?

Almost every JWT library has this option. Always pass it explicitly. Don’t trust the library’s default — defaults change across versions.

# PyJWT
import jwt
jwt.decode(token, public_key, algorithms=["RS256"])  # ← required
// golang-jwt
token, err := jwt.Parse(tokenString, func(t *jwt.Token) (any, error) {
    if _, ok := t.Method.(*jwt.SigningMethodRSA); !ok {
        return nil, fmt.Errorf("unexpected method: %v", t.Header["alg"])
    }
    return publicKey, nil
})
// jsonwebtoken
import jwt from "jsonwebtoken";
jwt.verify(token, publicKey, { algorithms: ["RS256"] });
// jsonwebtoken (Rust)
use jsonwebtoken::{decode, Validation, Algorithm, DecodingKey};
let mut validation = Validation::new(Algorithm::RS256);
decode::<Claims>(token, &DecodingKey::from_rsa_pem(public_key_pem)?, &validation)?;

2. Are you rejecting alg: none?

The original alg-confusion attack (CVE-2015-2951 era) was via "alg": "none". Modern libraries reject it by default, but verify:

# Decode and check what your library does with this token
echo 'eyJhbGciOiJub25lIiwidHlwIjoiSldUIn0.eyJzdWIiOiIxIn0.' | base64 -d

(Use our JWT decoder to inspect any JWT including alg: none crafted ones.)

3. Is your verifier checking the JWK if you’re using one?

If you fetch the verification key from a JWKS endpoint:

// Bad — trusts the kid header to pick the key, but doesn't verify
// the key matches the alg
const jwks = fetchJWKS(issuerURL);
const key = jwks[token.header.kid];   // ← could be a key for a
                                       // different algorithm
verify(token, key);

// Good — pairs the algorithm with the correct key type
const key = jwks[token.header.kid];
if (key.kty !== expectedKty(expectedAlg)) {
  throw new Error("alg/kty mismatch");
}
verify(token, expectedAlg, key);

4. Do you have logs that would let you detect a wave of forged tokens?

If a botnet starts probing your endpoint with alg: HS256 tokens forged using your public key, would you notice? At minimum:

Test your library yourself

You can replicate the alg-confusion attack against your own verifier in 30 seconds:

  1. Take your service’s RSA public key.
  2. Compute header = base64url('{"alg":"HS256","typ":"JWT"}').
  3. Compute payload = base64url('{"sub":"admin","exp":99999999999}').
  4. Compute sig = base64url(HMAC-SHA256(header + "." + payload, public_key)).
  5. Submit header.payload.sig to your verification endpoint.

If the endpoint accepts the token: you have an alg-confusion bug.

You can do steps 2–4 manually with our JWT signer (set the algorithm to HS256 and paste the public key bytes as the secret). Or with openssl and printf if you prefer the CLI.

When to rotate keys

Algorithm-confusion bugs are about the verifier’s logic, not about the key being stolen. If your verifier had the bug, you don’t necessarily need to rotate keys — the attack didn’t expose your private key. You need to:

  1. Update the library.
  2. Audit logs for any successful verification of a token with unexpected alg. If found, treat as a breach (rotate, force re-auth, etc.).

If your private key was potentially exposed by any mechanism (a different bug, a leaked credential, a compromised CI), then rotate regardless. See our JWK rotation playbook (coming).

The big picture

Vulnerabilities in JWT libraries are mostly about API ergonomics, not cryptography. The crypto primitives are sound. The bugs come from verifier APIs that let the caller forget to pin algorithms, forget to check key types, or forget to reject alg: none.

The defensive pattern is simple and works across every language:

Always specify the expected algorithm. Never trust the token’s own header. Audit every code path that loads keys.

Five lines of test code can detect every alg-confusion variant. Spend the time.

Further reading