FlawOpen

flawopen.com/Teardowns/cve-2026-107722-fast-jwt-rsa-hs256-algorithm-confusion

● CVE-2026-107722 · CVSS 9.8[1] · Critical
FlawOpen Security Research

CVE-2026-107722: fast-jwt RSA-to-HS256 Algorithm Confusion

In fast-jwt 6.2.0–6.2.4, a non-whitespace prefix could move an RSA PEM header past a start-anchored detector. In affected verifier configurations, the key could fall through to HMAC handling and permit JWT signature confusion. Version 6.3.0 fixes key classification; callers should also allow only the expected asymmetric algorithms.[1], [4]

💡 Plain English Explainer (ELI5)

Think of a mailroom that recognizes a sealed official envelope only when its label is the first thing on the page. A note pasted before the label makes the clerk treat the whole page as an ordinary handwritten message. fast-jwt had a similar recognition gap: text before a PEM header could make an RSA public key look like an HMAC secret. The patch finds the key header first and refuses unknown key blocks instead of silently changing key types.[1], [3]

Core Concepts & Subsystem Terms

PEM
A text encoding commonly used to store cryptographic keys and certificates between BEGIN/END markers.[2]
RSA public key
Public verification material for an asymmetric signature algorithm such as RS256; it is not a secret HMAC key.[1]
HS256
A JWT signing algorithm that uses a shared secret with HMAC-SHA-256.[1]
Algorithm confusion
A verification failure where a token or key is interpreted under a different cryptographic algorithm than the application intended.[1]
Algorithm allowlist
A verifier configuration that accepts only explicitly selected signing algorithms.[1]

Framework mappings

  • CWE reference for the weakness described in this guide: CWE-347

Root Cause Analysis

In fast-jwt 6.2.0–6.2.4, performDetectPublicKeyAlgorithms() trimmed the input and matched publicKeyPemMatcher, whose ^ anchor required the PEM header at position zero. If a non-whitespace prefix remained, the match failed; when no certificate marker was present, the fallback returned hsAlgorithms. That crossed a trust boundary: RSA public verification material could be treated as an HMAC secret. The advisory's vulnerable configurations do not enforce an asymmetric-only algorithms allowlist and obtain the verification key from a source that may contain a prefix. The upstream fix adds locatePem() and makes both key detectors classify from the located PEM header; unrecognized PEM material is rejected rather than silently treated as a raw secret. This teardown describes the code path, not an independently executed exploit. The vulnerable configuration lacks an asymmetric-only algorithms allowlist; the advisory also marks an HS256-only allowlist as vulnerable. The upstream locatePem helper is shared by both public-key and private-key detection. The advisory credits DC INFOSEC and EchoSkorJjj, and its body also names DC INFOSEC / n0l3x as reporter.[1], [2], [3]

Advisory-reported reachability (not independently reproduced)

Key/configurationAdvisory findingDefensive action
6.2.0–6.2.4; prefixed RSA PEM; no asymmetric-only allowlist, including HS256 allowed.In scope: failed PEM detection can fall back to HMAC. The advisory marks HS256-only as vulnerable.Upgrade to 6.3.0+; allow only the deployed asymmetric algorithm, such as RS256.
6.2.0–6.2.4; asymmetric-only RS256 allowlist enforced.The advisory says this configuration is protected from this confusion, including with a prefix.Keep the explicit allowlist and upgrade; the allowlist does not replace the library fix.
6.2.0–6.2.4; no non-whitespace prefix before PEM.The specific CVE-2026-107722 trigger is absent; the affected package version is still not fixed.Upgrade and validate the exact loaded key bytes and format.
6.3.0 or later; RSA verification with asymmetric-only allowlist.The upstream fix locates PEM headers and rejects unsupported PEM instead of using HMAC fallback.Use a maintained release and regression-test prefixed RSA/EC key classification without forged tokens.
Deployment declares 6.3.0+, but the lockfile or runtime artifact resolves a nested fast-jwt copy below 6.3.0.That resolved vulnerable copy remains exposed; a package.json range alone does not prove the deployed runtime is fixed.Inspect the lockfile and each deployed server artifact; require every resolved fast-jwt copy to be 6.3.0 or later.

Step-by-Step Attack Flow

Step 1

A verifier loads its key

An application using an affected fast-jwt release obtains an RSA public key from configuration or another source that can add non-whitespace text before the PEM header.[1]

Step 2

The anchored detector misses the PEM

trim() removes whitespace, but the leading non-whitespace content remains, so the start-anchored public-key matcher does not recognize the RSA header.[2]

Step 3

The fallback changes key class

Without an asymmetric-only algorithm allowlist, the detector can classify the nonmatching string as an HMAC secret. This is the vulnerable boundary described by the advisory; it is not a claim that every fast-jwt deployment is exploitable.[1]

Step 4

Verification accepts the wrong algorithm

A token using an HMAC algorithm may be checked with the same public-key bytes as its shared secret, enabling authentication or authorization impact in the affected configuration.[1]

Step 5

The patch preserves key type

Version 6.3.0 locates the PEM header, uses the content from that point for key detection, and rejects unsupported PEM blocks. An asymmetric-only algorithm allowlist adds a separate verification boundary.[3], [4]

Source Code: Flaw vs. Secure Implementation

These are focused excerpts, not drop-in replacements. The fix distinguishes a raw HMAC secret from input containing a PEM header, classifies the located key, and rejects unsupported PEM material rather than falling back to HMAC. The same helper is used by the private-key detector. Configure the caller to allow only its intended asymmetric algorithms as defense in depth.[2], [3]

Source excerpt: src/crypto.js (focused excerpts)

UNPATCHED FLAW
// fast-jwt 6.2.4: focused excerpt from performDetectPublicKeyAlgorithms()
const trimmedKey = key.trim()
const publicKeyPemMatch = trimmedKey.match(publicKeyPemMatcher)

if (publicKeyPemMatch && publicKeyPemMatch[1] === 'RSA') {
  return rsaAlgorithms
} else if (!publicKeyPemMatch && !trimmedKey.includes(publicKeyX509CertMatcher)) {
  // No recognized PEM or certificate: assume an HMAC secret.
  return hsAlgorithms
}
HARDENED SECURE PATCH
// fast-jwt 6.3.0: focused excerpt from the upstream key-detection fix
function locatePem(trimmedKey) {
  const pemStart = trimmedKey.search(pemBeginMatcher)
  if (pemStart === -1) return { pem: null, isRawSecret: true }
  return { pem: trimmedKey.slice(pemStart), isRawSecret: false }
}

const { pem, isRawSecret } = locatePem(key.trim())
if (isRawSecret) return hsAlgorithms
const publicKeyPemMatch = pem.match(publicKeyPemMatcher)
if (publicKeyPemMatch && publicKeyPemMatch[1] === 'RSA') {
  return rsaAlgorithms
} else if (!publicKeyPemMatch && !pem.startsWith(publicKeyX509CertMatcher)) {
  throw new TokenError(TokenError.codes.invalidKey, 'Unsupported PEM public key.')
}

Engineering & System Hardening Checklist

Sources