GitHub Security Advisory: GHSA-mvf2-f6gm-w987
Package: fast-jwt (NPM)
Severity: CRITICAL (CVSS 9.1)
CWE: CWE-20: Improper Input Validation, CWE-327: Use of a Broken or Risky Cryptographic Algorithm
Vulnerable versions: <= 6.1.0
Patched in: 6.2.0

Summary: fast-jwt: Incomplete fix for CVE-2023-48223: JWT Algorithm Confusion via Whitespace-Prefixed RSA Public Key

Details:
### Summary
 The fix for GHSA-c2ff-88x2-x9pg (CVE-2023-48223) is incomplete. The publicKeyPemMatcher regex in fast-jwt/src/crypto.js uses a ^ anchor that is defeated by any leading whitespace in the key string, re-enabling the exact same JWT algorithm confusion attack that the CVE patched.

### Details
 The fix for CVE-2023-48223 (https://github.com/nearform/fast-jwt/commit/15a6e92, v3.3.2) changed the public key matcher from a
  plain string used with .includes() to a regex used with .match():

```
  // Before fix (vulnerable to original CVE)
  const publicKeyPemMatcher = '-----BEGIN PUBLIC KEY-----'
  // .includes() matched anywhere in the string — not vulnerable to whitespace

  // After fix (current code, line 28)
  const publicKeyPemMatcher = /^-----BEGIN(?: (RSA))? PUBLIC KEY-----/
  // ^ anchor requires match at position 0 — defeated by leading whitespace

  In performDetectPublicKeyAlgorithms()
  (https://github.com/nearform/fast-jwt/blob/0ff14a687b9af786bd3ffa870d6febe6e1f13aaa/src/crypto.js#L126-L137):

  function performDetectPublicKeyAlgorithms(key) {
    const publicKeyPemMatch = key.match(publicKeyPemMatcher)  // no .trim()!

    if (key.match(privateKeyPemMatcher)) {
      throw ...
    } else if (publicKeyPemMatch && publicKeyPemMatch[1] === 'RSA') {
      return rsaAlgorithms      // ← correct path: restricts to RS/PS algorithms
    } else if (!publicKeyPemMatch && !key.includes(publicKeyX509CertMatcher)) {
      return hsAlgorithms        // ← VULNERABLE: RSA key falls through here
    }

```
  When the key string has any leading whitespace (space, tab, \n, \r\n), the ^ anchor fails, publicKeyPemMatch is null, and the RSA
  public key is classified as an HMAC secret (hsAlgorithms). The attacker can then sign an HS256 token using the public key as the
  HMAC secret — the exact same attack as CVE-2023-48223.

  Notably, the private key detection function does call .trim() before matching
  https://github.com/nearform/fast-jwt/blob/0ff14a687b9af786bd3ffa870d6febe6e1f13aaa/src/crypto.js#L79:
const pemData = key.trim().match(privateKeyPemMatcher)  // trims — not vulnerable

  The public key path does not. This inconsistency is the root cause.

  Leading whitespace in PEM key strings is common in real-world deployments:
  - PostgreSQL/MySQL text columns often return strings with leading newlines
  - YAML multiline strings (|, >) can introduce leading whitespace
  - Environment variables with embedded newlines
  - Copy-paste into configuration files

### PoC
 Victim server (server.js):

```
  const http = require('node:http');
  const { generateKeyPairSync } = require('node:crypto');
  const fs = require('node:fs');
  const path = require('node:path');
  const { createSigner, createVerifier } = require('fast-jwt');

  const port = 3000;

  // Generate RSA key pair
  const { publicKey, privateKey } = generateKeyPairSync('rsa', { modulusLength: 2048 });
  const publicKeyPem = publicKey.export({ type: 'pkcs1', format: 'pem' });
  const pr