{"id":"CVE-2026-32597","aliases":["GHSA-752w-5fwx-jx9f","PYSEC-2026-120"],"url":"https://o3.security/vulnerability/CVE-2026-32597","summary":"PyJWT accepts unknown `crit` header extensions (RFC 7515 §4.1.11 MUST violation)","details":"## Summary\n\nPyJWT does not validate the `crit` (Critical) Header Parameter defined in\nRFC 7515 §4.1.11. When a JWS token contains a `crit` array listing\nextensions that PyJWT does not understand, the library accepts the token\ninstead of rejecting it. This violates the **MUST** requirement in the RFC.\n\nThis is the same class of vulnerability as CVE-2025-59420 (Authlib),\nwhich received CVSS 7.5 (HIGH).\n\n---\n\n## RFC Requirement\n\nRFC 7515 §4.1.11:\n\n> The \"crit\" (Critical) Header Parameter indicates that extensions to this\n> specification and/or [JWA] are being used that **MUST** be understood and\n> processed. [...] If any of the listed extension Header Parameters are\n> **not understood and supported** by the recipient, then the **JWS is invalid**.\n\n---\n\n## Proof of Concept\n\n```python\nimport jwt  # PyJWT 2.8.0\nimport hmac, hashlib, base64, json\n\n# Construct token with unknown critical extension\nheader = {\"alg\": \"HS256\", \"crit\": [\"x-custom-policy\"], \"x-custom-policy\": \"require-mfa\"}\npayload = {\"sub\": \"attacker\", \"role\": \"admin\"}\n\ndef b64url(data):\n    return base64.urlsafe_b64encode(data).rstrip(b\"=\").decode()\n\nh = b64url(json.dumps(header, separators=(\",\", \":\")).encode())\np = b64url(json.dumps(payload, separators=(\",\", \":\")).encode())\nsig = b64url(hmac.new(b\"secret\", f\"{h}.{p}\".encode(), hashlib.sha256).digest())\ntoken = f\"{h}.{p}.{sig}\"\n\n# Should REJECT — x-custom-policy is not understood by PyJWT\ntry:\n    result = jwt.decode(token, \"secret\", algorithms=[\"HS256\"])\n    print(f\"ACCEPTED: {result}\")\n    # Output: ACCEPTED: {'sub': 'attacker', 'role': 'admin'}\nexcept Exception as e:\n    print(f\"REJECTED: {e}\")\n```\n\n**Expected:** `jwt.exceptions.InvalidTokenError: Unsupported critical extension: x-custom-policy`\n**Actual:** Token accepted, payload returned.\n\n### Comparison with RFC-compliant library\n\n```python\n# jwcrypto — correctly rejects\nfrom jwcrypto import jwt as jw_jwt, jwk\nkey = jwk.JWK(kty=\"oct\", k=b64url(b\"secret\"))\njw_jwt.JWT(jwt=token, key=key, algs=[\"HS256\"])\n# raises: InvalidJWSObject('Unknown critical header: \"x-custom-policy\"')\n```\n\n---\n\n## Impact\n\n- **Split-brain verification** in mixed-library deployments (e.g., API\n  gateway using jwcrypto rejects, backend using PyJWT accepts)\n- **Security policy bypass** when `crit` carries enforcement semantics\n  (MFA, token binding, scope restrictions)\n- **Token binding bypass** — RFC 7800 `cnf` (Proof-of-Possession) can be\n  silently ignored\n- See CVE-2025-59420 for full impact analysis\n\n---\n\n## Suggested Fix\n\nIn `jwt/api_jwt.py`, add validation in `_validate_headers()` or\n`decode()`:\n\n```python\n_SUPPORTED_CRIT = {\"b64\"}  # Add extensions PyJWT actually supports\n\ndef _validate_crit(self, headers: dict) -> None:\n    crit = headers.get(\"crit\")\n    if crit is None:\n        return\n    if not isinstance(crit, list) or len(crit) == 0:\n        raise InvalidTokenError(\"crit must be a non-empty array\")\n    for ext in crit:\n        if ext not in self._SUPPORTED_CRIT:\n            raise InvalidTokenError(f\"Unsupported critical extension: {ext}\")\n        if ext not in headers:\n            raise InvalidTokenError(f\"Critical extension {ext} not in header\")\n```\n\n---\n\n## CWE\n\n- CWE-345: Insufficient Verification of Data Authenticity\n- CWE-863: Incorrect Authorization\n\n## References\n\n- [RFC 7515 §4.1.11](https://www.rfc-editor.org/rfc/rfc7515.html#section-4.1.11)\n- [CVE-2025-59420 — Authlib crit bypass (CVSS 7.5)](https://osv.dev/vulnerability/GHSA-9ggr-2464-2j32)\n- [RFC 7800 — Proof-of-Possession Key Semantics](https://www.rfc-editor.org/rfc/rfc7800)","published":"2026-03-12T21:41:50.427Z","modified":"2026-11-02T09:42:00.821863603Z","cvss":{"score":7.5,"severity":"HIGH","vector":"CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:H/A:N"},"epss":null,"cisaKev":null,"exploitsKnown":null,"affectedPackages":[{"ecosystem":"PyPI","name":"pyjwt","fixedVersion":"2.12.0"}],"fix":null,"references":[{"type":"WEB","url":"https://lists.debian.org/debian-lts-announce/2026/05/msg00008.html"},{"type":"WEB","url":"https://security.access.redhat.com/data/csaf/v2/vex/2026/cve-2026-32597.json"},{"type":"ADVISORY","url":"https://access.redhat.com/errata/RHSA-2026:10140"},{"type":"ADVISORY","url":"https://access.redhat.com/errata/RHSA-2026:10141"},{"type":"ADVISORY","url":"https://access.redhat.com/errata/RHSA-2026:10184"},{"type":"ADVISORY","url":"https://access.redhat.com/errata/RHSA-2026:12176"},{"type":"ADVISORY","url":"https://access.redhat.com/errata/RHSA-2026:13508"},{"type":"ADVISORY","url":"https://access.redhat.com/errata/RHSA-2026:13512"},{"type":"ADVISORY","url":"https://access.redhat.com/errata/RHSA-2026:13545"},{"type":"ADVISORY","url":"https://access.redhat.com/errata/RHSA-2026:13553"},{"type":"ADVISORY","url":"https://access.redhat.com/errata/RHSA-2026:13672"},{"type":"ADVISORY","url":"https://access.redhat.com/errata/RHSA-2026:13916"},{"type":"ADVISORY","url":"https://access.redhat.com/errata/RHSA-2026:17083"},{"type":"ADVISORY","url":"https://access.redhat.com/errata/RHSA-2026:19138"},{"type":"ADVISORY","url":"https://access.redhat.com/errata/RHSA-2026:19355"},{"type":"ADVISORY","url":"https://access.redhat.com/errata/RHSA-2026:19375"},{"type":"ADVISORY","url":"https://access.redhat.com/errata/RHSA-2026:19712"},{"type":"ADVISORY","url":"https://access.redhat.com/errata/RHSA-2026:21431"},{"type":"ADVISORY","url":"https://access.redhat.com/errata/RHSA-2026:21517"},{"type":"ADVISORY","url":"https://access.redhat.com/errata/RHSA-2026:22330"},{"type":"ADVISORY","url":"https://access.redhat.com/errata/RHSA-2026:24977"},{"type":"ADVISORY","url":"https://access.redhat.com/errata/RHSA-2026:26226"},{"type":"ADVISORY","url":"https://access.redhat.com/errata/RHSA-2026:37275"},{"type":"ADVISORY","url":"https://access.redhat.com/errata/RHSA-2026:42644"},{"type":"ADVISORY","url":"https://access.redhat.com/errata/RHSA-2026:6568"},{"type":"ADVISORY","url":"https://access.redhat.com/errata/RHSA-2026:6720"},{"type":"ADVISORY","url":"https://access.redhat.com/errata/RHSA-2026:6912"},{"type":"ADVISORY","url":"https://access.redhat.com/errata/RHSA-2026:6926"},{"type":"ADVISORY","url":"https://access.redhat.com/errata/RHSA-2026:8437"},{"type":"ADVISORY","url":"https://access.redhat.com/errata/RHSA-2026:8746"},{"type":"ADVISORY","url":"https://access.redhat.com/errata/RHSA-2026:8747"},{"type":"ADVISORY","url":"https://access.redhat.com/errata/RHSA-2026:8748"},{"type":"ADVISORY","url":"https://access.redhat.com/security/cve/CVE-2026-32597"},{"type":"ADVISORY","url":"https://github.com/CVEProject/cvelistV5/tree/main/cves/2026/32xxx/CVE-2026-32597.json"},{"type":"ADVISORY","url":"https://github.com/jpadilla/pyjwt/security/advisories/GHSA-752w-5fwx-jx9f"},{"type":"ADVISORY","url":"https://nvd.nist.gov/vuln/detail/CVE-2026-32597"},{"type":"REPORT","url":"https://bugzilla.redhat.com/show_bug.cgi?id=2447194"},{"type":"PACKAGE","url":"https://github.com/jpadilla/pyjwt"},{"type":"WEB","url":"https://github.com/pypa/advisory-database/tree/main/vulns/pyjwt/PYSEC-2026-120.yaml"}],"provenance":{"sources":["OSV.dev","FIRST.org (EPSS)"],"lastVerified":"2026-11-02T09:42:00.821863603Z"}}