Skip to content

fix(deps): update dependency pyjwt to v2.12.0 [security]#589

Merged
gjtorikian merged 2 commits intomainfrom
renovate/pypi-pyjwt-vulnerability
Mar 16, 2026
Merged

fix(deps): update dependency pyjwt to v2.12.0 [security]#589
gjtorikian merged 2 commits intomainfrom
renovate/pypi-pyjwt-vulnerability

Conversation

@renovate
Copy link
Contributor

@renovate renovate bot commented Mar 14, 2026

This PR contains the following updates:

Package Change Age Confidence
pyjwt >=2.9.0,<2.10 >=2.12,<2.13 age confidence
pyjwt 2.10.12.12.0 age confidence

GitHub Vulnerability Alerts

CVE-2026-32597

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


Release Notes

jpadilla/pyjwt (pyjwt)

v2.12.0

Compare Source

Fixed


- Annotate PyJWKSet.keys for pyright by @&#8203;tamird in `#&#8203;1134 <https://github.com/jpadilla/pyjwt/pull/1134>`__
- Close ``HTTPError`` response to prevent ``ResourceWarning`` on Python 3.14 by @&#8203;veeceey in `#&#8203;1133 <https://github.com/jpadilla/pyjwt/pull/1133>`__
- Do not keep ``algorithms`` dict in PyJWK instances by @&#8203;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 @&#8203;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

Compare Source

Fixed


Added

Configuration

📅 Schedule: Branch creation - "" in timezone UTC, Automerge - At any time (no schedule defined).

🚦 Automerge: Enabled.

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

🔕 Ignore: Close this PR and you won't be reminded about these updates 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 enabled auto-merge (squash) March 14, 2026 01:52
@renovate renovate bot requested review from a team as code owners March 14, 2026 01:52
@renovate renovate bot requested a review from gjtorikian March 14, 2026 01:52
@renovate
Copy link
Contributor Author

renovate bot commented Mar 14, 2026

⚠️ Artifact update problem

Renovate failed to update an artifact related to this branch. You probably do not want to merge this PR as-is.

♻ Renovate will retry this branch, including artifacts, only when one of the following happens:

  • any of the package files in this branch needs updating, or
  • the branch becomes conflicted, or
  • you click the rebase/retry checkbox if found above, or
  • you rename this PR's title to start with "rebase!" to trigger it manually

The artifact failure details are included below:

File name: uv.lock
Command failed: uv lock --upgrade-package pyjwt --upgrade-package pyjwt
Using CPython 3.14.3 interpreter at: /opt/containerbase/tools/python/3.14.3/bin/python3
  × No solution found when resolving dependencies for split (markers:
  │ python_full_version == '3.8.*'):
  ╰─▶ Because only the following versions of pyjwt{python_full_version <
      '3.9'} are available:
          pyjwt{python_full_version < '3.9'}<=2.12.0
          pyjwt{python_full_version < '3.9'}==2.12.1
      and the requested Python version (>=3.8) does not satisfy Python>=3.9,
      we can conclude that pyjwt{python_full_version < '3.9'}>=2.12.0 cannot
      be used.
      And because your project depends on pyjwt{python_full_version <
      '3.9'}>=2.12, we can conclude that your project's requirements are
      unsatisfiable.

      hint: While the active Python version is 3.14, the resolution failed for
      other Python versions supported by your project. Consider limiting your
      project's supported Python versions using `requires-python`.

@greptile-apps
Copy link
Contributor

greptile-apps bot commented Mar 14, 2026

Greptile Summary

This PR addresses the critical JWT security vulnerability CVE-2026-32597 (missing crit header parameter validation) by raising the PyJWT lower bound for Python 3.9+ from >=2.10.0 to >=2.12.0, and regenerating uv.lock so it now resolves to pyjwt 2.12.1 (the latest 2.12.x patch, which supersedes the 2.12.0 named in the PR title). The lock file update is essential and is correctly included — Python 3.9+ users running uv sync will receive the patched version.

Key observations:

  • Security fix properly applied for Python 3.9+: Both pyproject.toml (>=2.12.0) and uv.lock (2.12.1) are correctly updated.
  • Lock resolves to 2.12.1, not 2.12.0: The PR title says "v2.12.0" but the lock pins to 2.12.1 (released 2026-03-13), which is a strictly better outcome since it includes additional fixes.
  • New transitive dependency introduced: pyjwt 2.12.1 introduces typing-extensions 4.15.0 as a runtime dependency for Python 3.9–3.10 (visible in uv.lock). This is handled transparently by the regenerated lock file, but it is a new transitive dependency that wasn't present before.
  • Python 3.8 users remain on vulnerable pyjwt 2.9.0: The python_full_version == '3.8.*' constraint was not updated and still resolves to the vulnerable 2.9.0. Since Python 3.8 has been EOL since October 2024, dropping 3.8 support entirely would both close this gap and simplify the dependency matrix.

Confidence Score: 3/5

  • Safe to merge for Python 3.9+ users; Python 3.8 users remain exposed to CVE-2026-32597.
  • The security fix is correctly and completely applied for Python 3.9+ (both specifier and lock file), which covers the vast majority of users. However, Python 3.8 — still declared as supported in requires-python = ">=3.8" — remains pinned to the vulnerable pyjwt 2.9.0 range. Until Python 3.8 support is explicitly dropped or the vulnerability acknowledged as accepted risk for that platform, the fix is incomplete for a supported Python version.
  • pyproject.toml — the Python 3.8 pyjwt constraint (>=2.9.0,<2.10) leaves 3.8 users on a vulnerable version while requires-python still advertises 3.8 support.

Important Files Changed

Filename Overview
pyproject.toml Raises the PyJWT lower bound for Python 3.9+ from >=2.10.0 to >=2.12.0, closing CVE-2026-32597. Python 3.8 users remain constrained to >=2.9.0,<2.10 (still vulnerable).
uv.lock Lock file regenerated; PyJWT resolved to 2.12.1 (beyond the stated 2.12.0 target, which is fine) for Python 3.9+. Introduces a new transitive dependency on typing-extensions 4.15.0 for Python 3.9–3.10. Python 3.8 still pinned to 2.9.0.

Flowchart

%%{init: {'theme': 'neutral'}}%%
flowchart TD
    A[Install workos-python] --> B{Python Version?}
    B -- "Python 3.8" --> C["pyjwt >=2.9.0,<2.10\n(resolves to 2.9.0)"]
    B -- "Python 3.9+" --> D["pyjwt >=2.12.0\n(resolves to 2.12.1)"]
    C --> E["⚠️ CVE-2026-32597\nVulnerable — crit header bypass"]
    D --> F["✅ CVE-2026-32597 Fixed\n(crit header validation added)"]
    D --> G["New transitive dep:\ntyping-extensions 4.15.0\n(Python 3.9–3.10 only)"]
Loading

Last reviewed commit: d6fac2d

PyJWT 2.12 dropped Python 3.8 support, so the Renovate-generated
constraint was unsatisfiable for Python 3.8. Keep the old pin for 3.8
and apply the security update (CVE-2026-32597) only for Python >=3.9.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@renovate
Copy link
Contributor Author

renovate bot commented Mar 16, 2026

Edited/Blocked Notification

Renovate will not automatically rebase this PR, because it does not recognize the last commit author and assumes somebody else may have edited the PR.

You can manually request rebase by checking the rebase/retry box above.

⚠️ Warning: custom changes will be lost.

@gjtorikian gjtorikian closed this Mar 16, 2026
auto-merge was automatically disabled March 16, 2026 19:58

Pull request was closed

@gjtorikian gjtorikian reopened this Mar 16, 2026
@gjtorikian gjtorikian enabled auto-merge (squash) March 16, 2026 19:58
@gjtorikian gjtorikian merged commit 0d50534 into main Mar 16, 2026
19 checks passed
@gjtorikian gjtorikian deleted the renovate/pypi-pyjwt-vulnerability branch March 16, 2026 19:59
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Development

Successfully merging this pull request may close these issues.

1 participant