jwt.verify accepts tokens with unrecognized `crit` header extensions (RFC 7515 §4.1.11)
Description
[email protected] accepts JWTs whose JOSE header contains a crit array listing extension Header Parameters that the library does not understand or process. Per RFC 7515 §4.1.11, such tokens must be rejected:
If any of the listed extension Header Parameters are not understood and supported by the recipient, then the JWS MUST be rejected.
A grep of the installed [email protected] source shows no crit handling at all — the field is silently ignored during verification. As a result, a signer who relies on crit to mandate that verifiers respect an extension (e.g. a key-binding or policy header) gets no enforcement when the verifier is jsonwebtoken, even though the signer marked the extension as critical.
Reproduction
// npm i [email protected]
const jwt = require('jsonwebtoken');
const crypto = require('crypto');
const { publicKey, privateKey } = crypto.generateKeyPairSync('rsa', { modulusLength: 2048 });
const pubPem = publicKey.export ({ type: 'spki', format: 'pem' });
const privPem = privateKey.export({ type: 'pkcs8', format: 'pem' });
// Sign a token that declares an extension param as critical
const token = jwt.sign(
{ sub: 'x', role: 'admin' },
privPem,
{ algorithm: 'RS256',
header: { crit: ['x-attack-vector'], 'x-attack-vector': true } });
// Verify — RFC 7515 §4.1.11 requires rejection because jsonwebtoken does not
// understand or process the 'x-attack-vector' extension.
console.log(jwt.verify(token, pubPem, { algorithms: ['RS256'] }));Observed output:
{ sub: 'x', role: 'admin', iat: 1779849601 }Expected: a verification error (e.g. JsonWebTokenError: critical header parameter 'x-attack-vector' is not understood).
Cross-library comparison
[email protected] rejects the same token, citing the same RFC clause:
ERR_JOSE_NOT_SUPPORTED: Extension Header Parameter "x-attack-vector" is not recognizedSo a deployment that issues tokens with crit-protected extensions via one library and verifies them with jsonwebtoken loses the protection the signer intended.
Why it matters
crit is the mechanism JWS provides for a signer to require that a particular extension header be honored. Concrete examples in the wild:
b64(RFC 7797) — signer asserts the payload is not base64url-encoded; a verifier ignoringcritwill treat it as encoded and either fail or, worse, validate a different payload than the one signed.- Custom security-policy extensions (e.g. token-binding hints, audience-restriction extensions, replay-protection nonces) where the signer explicitly demands enforcement.
A verifier that silently strips crit defeats the whole purpose of the field as defined in RFC 7515.
Suggested fix direction
In verify.js, after the header is decoded:
- If
header.critexists, it must be a non-empty array of strings (per §4.1.11). - Reject if
critlists any name that this library does not implement. Sincejsonwebtokendoes not implement anycritextensions, the simplest correct behavior is to reject any token whosecritis non-empty unless the caller opts in via an option likecrit: ['name1', ...]enumerating extensions the caller has handled out-of-band.
jose's implementation is a small reference.
Environment
jsonwebtoken9.0.3 (current latest on npm)- Node v26.0.0
- macOS 14
Source: auth0/node-jsonwebtoken