Description
jsonwebtoken@9.0.3 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 jsonwebtoken@9.0.3 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 jsonwebtoken@9.0.3
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
jose@5.10.0 rejects the same token, citing the same RFC clause:
ERR_JOSE_NOT_SUPPORTED: Extension Header Parameter "x-attack-vector" is not recognized
So 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 ignoring crit will 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.crit exists, it must be a non-empty array of strings (per §4.1.11).
- Reject if
crit lists any name that this library does not implement. Since jsonwebtoken does not implement any crit extensions, the simplest correct behavior is to reject any token whose crit is non-empty unless the caller opts in via an option like crit: ['name1', ...] enumerating extensions the caller has handled out-of-band.
jose's implementation is a small reference.
Environment
jsonwebtoken 9.0.3 (current latest on npm)
- Node v26.0.0
- macOS 14
Description
jsonwebtoken@9.0.3accepts JWTs whose JOSE header contains acritarray listing extension Header Parameters that the library does not understand or process. Per RFC 7515 §4.1.11, such tokens must be rejected:A grep of the installed
jsonwebtoken@9.0.3source shows nocrithandling at all — the field is silently ignored during verification. As a result, a signer who relies oncritto mandate that verifiers respect an extension (e.g. a key-binding or policy header) gets no enforcement when the verifier isjsonwebtoken, even though the signer marked the extension as critical.Reproduction
Observed output:
Expected: a verification error (e.g.
JsonWebTokenError: critical header parameter 'x-attack-vector' is not understood).Cross-library comparison
jose@5.10.0rejects the same token, citing the same RFC clause:So a deployment that issues tokens with
crit-protected extensions via one library and verifies them withjsonwebtokenloses the protection the signer intended.Why it matters
critis 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.A verifier that silently strips
critdefeats the whole purpose of the field as defined in RFC 7515.Suggested fix direction
In
verify.js, after the header is decoded:header.critexists, it must be a non-empty array of strings (per §4.1.11).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)