Lack of data validation In pyjwt

Description

PyJWT accepts unknown crit header extensions

Summary

PyJWT does not validate the crit (Critical) Header Parameter defined in RFC 7515 §4.1.11. When a JWS token contains a crit array listing extensions that PyJWT does not understand, the library accepts the token instead of rejecting it. This violates the MUST requirement in the RFC.

This is the same class of vulnerability as CVE-2025-59420 (Authlib), which received CVSS 7.5 (HIGH).


RFC Requirement

RFC 7515 §4.1.11:

The "crit" (Critical) Header Parameter indicates that extensions to this specification and/or [JWA] are being used that MUST be understood and processed. [...] If any of the listed extension Header Parameters are not understood and supported by the recipient, then the JWS is invalid.


Proof of Concept

import jwt  # PyJWT 2.8.0
import hmac, hashlib, base64, json

# Construct token with unknown critical extension
header = {"alg": "HS256", "crit": ["x-custom-policy"], "x-custom-policy": "require-mfa"}
payload = {"sub": "attacker", "role": "admin"}

def b64url(data):...

Expected: jwt.exceptions.InvalidTokenError: Unsupported critical extension: x-custom-policy Actual: Token accepted, payload returned.

Comparison with RFC-compliant library

# jwcrypto — correctly rejects
from jwcrypto import jwt as jw_jwt, jwk
key = jwk.JWK(kty="oct", k=b64url(b"secret"))
jw_jwt.JWT(jwt=token, key=key, algs=["HS256"])
# raises: InvalidJWSObject('Unknown critical header: "x-custom-policy"')

Impact

    Split-brain verification in mixed-library deployments (e.g., API gateway using jwcrypto rejects, backend using PyJWT accepts)

    Security policy bypass when crit carries enforcement semantics (MFA, token binding, scope restrictions)

    Token binding bypass — RFC 7800 cnf (Proof-of-Possession) can be silently ignored

    See CVE-2025-59420 for full impact analysis


Suggested Fix

In jwt/api_jwt.py, add validation in _validate_headers() or decode():

_SUPPORTED_CRIT = {"b64"}  # Add extensions PyJWT actually supports

def _validate_crit(self, headers: dict) -> None:
    crit = headers.get("crit")
    if crit is None:
        return
    if not isinstance(crit, list) or len(crit) == 0:
        raise InvalidTokenError("crit must be a non-empty array")...

CWE

    CWE-345: Insufficient Verification of Data Authenticity

    CWE-863: Incorrect Authorization

References

Mitigation

Update Impact

Minimal update. May introduce new vulnerabilities or breaking changes.

Ecosystem
Package
Affected version
Patched versions

1-10 of 11

10