[CU-86b4umhm1] Update dependency pyjwt to v2.12.0 [SECURITY] by dnastack-renovate[bot] · Pull Request #159 · DNAstack/dnastack-client
This PR contains the following updates:
| Package | Change | Age | Adoption | Passing | Confidence |
|---|---|---|---|---|---|
| pyjwt | 2.10.1 → 2.12.0 |
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
critcarries 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
- RFC 7515 §4.1.11
- CVE-2025-59420 — Authlib crit bypass (CVSS 7.5)
- RFC 7800 — Proof-of-Possession Key Semantics
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
critcarries 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
- RFC 7515 §4.1.11
- CVE-2025-59420 — Authlib crit bypass (CVSS 7.5)
- RFC 7800 — Proof-of-Possession Key Semantics
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
- https://github.com/jpadilla/pyjwt/security/advisories/GHSA-752w-5fwx-jx9f
- https://nvd.nist.gov/vuln/detail/CVE-2026-32597
- https://github.com/jpadilla/pyjwt
This data is provided by OSV and the GitHub Advisory Database (CC-BY 4.0).
Release Notes
jpadilla/pyjwt (pyjwt)
v2.12.0
Security
- Validate the crit (Critical) Header Parameter defined in RFC 7515 §4.1.11. by @dmbs335 in GHSA-752w-5fwx-jx9f
What's Changed
- [pre-commit.ci] pre-commit autoupdate by @pre-commit-ci[bot] in #1132
- chore(docs): fix docs build by @tamird in #1137
- Annotate PyJWKSet.keys for pyright by @tamird in #1134
- fix: close HTTPError to prevent ResourceWarning on Python 3.14 by @veeceey in #1133
- chore: remove superfluous constants by @tamird in #1136
- [pre-commit.ci] pre-commit autoupdate by @pre-commit-ci[bot] in #1135
- chore(tests): enable mypy by @tamird in #1138
- Bump actions/download-artifact from 7 to 8 by @dependabot[bot] in #1142
- [pre-commit.ci] pre-commit autoupdate by @pre-commit-ci[bot] in #1141
- [pre-commit.ci] pre-commit autoupdate by @pre-commit-ci[bot] in #1145
- fix: do not store reference to algorithms dict on PyJWK by @akx in #1143
- Use PyJWK algorithm when encoding without explicit algorithm by @jpadilla in #1148
New Contributors
Full Changelog: jpadilla/pyjwt@2.11.0...2.12.0
v2.11.0
What's Changed
- Fixed type error in comment by @shuhaib-aot in #1026
- [pre-commit.ci] pre-commit autoupdate by @pre-commit-ci[bot] in #1018
- [pre-commit.ci] pre-commit autoupdate by @pre-commit-ci[bot] in #1033
- Make note of use of leeway with nbf by @djw8605 in #1034
- [pre-commit.ci] pre-commit autoupdate by @pre-commit-ci[bot] in #1035
- Fixes #964: Validate key against allowed types for Algorithm family by @pachewise in #985
- Feat #1024: Add iterator for PyJWKSet by @pachewise in #1041
- Fixes #1039: Add iss, issuer type checks by @pachewise in #1040
- Fixes #660: Improve typing/logic for
optionsin decode, decode_complete; Improve docs by @pachewise in #1045 - [pre-commit.ci] pre-commit autoupdate by @pre-commit-ci[bot] in #1042
- [pre-commit.ci] pre-commit autoupdate by @pre-commit-ci[bot] in #1052
- [pre-commit.ci] pre-commit autoupdate by @pre-commit-ci[bot] in #1053
- Fix #1022: Map
algorithm=Noneto "none" by @qqii in #1056 - [pre-commit.ci] pre-commit autoupdate by @pre-commit-ci[bot] in #1055
- [pre-commit.ci] pre-commit autoupdate by @pre-commit-ci[bot] in #1058
- [pre-commit.ci] pre-commit autoupdate by @pre-commit-ci[bot] in #1060
- [pre-commit.ci] pre-commit autoupdate by @pre-commit-ci[bot] in #1061
- Fixes #1047: Correct
PyJWKClient.get_signing_key_from_jwtannotation by @khvn26 in #1048 - [pre-commit.ci] pre-commit autoupdate by @pre-commit-ci[bot] in #1062
- Fixed doc string typo in _validate_jti() function #1063 by @kuldeepkhatke in #1064
- [pre-commit.ci] pre-commit autoupdate by @pre-commit-ci[bot] in #1065
- Update SECURITY.md by @auvipy in #1057
- Typing fix: use
floatinstead ofintforlifespanandtimeoutby @nikitagashkov in #1068 - [pre-commit.ci] pre-commit autoupdate by @pre-commit-ci[bot] in #1067
- [pre-commit.ci] pre-commit autoupdate by @pre-commit-ci[bot] in #1071
- [pre-commit.ci] pre-commit autoupdate by @pre-commit-ci[bot] in #1076
- Fix TYP header documentation by @fobiasmog in #1046
- doc: Document claims sub and jti by @cleder in #1088
- [pre-commit.ci] pre-commit autoupdate by @pre-commit-ci[bot] in #1077
- Bump actions/setup-python from 5 to 6 by @dependabot[bot] in #1089
- Bump actions/stale from 8 to 10 by @dependabot[bot] in #1090
- Bump actions/checkout from 4 to 5 by @dependabot[bot] in #1083
- [pre-commit.ci] pre-commit autoupdate by @pre-commit-ci[bot] in #1091
- [pre-commit.ci] pre-commit autoupdate by @pre-commit-ci[bot] in #1093
- [pre-commit.ci] pre-commit autoupdate by @pre-commit-ci[bot] in #1096
- Resolve package build warnings by @kurtmckee in #1105
- Support Python 3.14, and test against PyPy 3.10+ by @kurtmckee in #1104
- Fix a
SyntaxWarningcaused by invalid escape sequences by @kurtmckee in #1103 - Standardize CHANGELOG links to PRs by @kurtmckee in #1110
- Migrate from
pep517, which is deprecated, tobuildby @kurtmckee in #1108 - Fix incorrectly-named test suite function by @kurtmckee in #1116
- Fix Read the Docs builds by @kurtmckee in #1111
- Bump actions/download-artifact from 4 to 6 by @dependabot[bot] in #1118
- Escalate test suite warnings to errors by @kurtmckee in #1107
- Add pyupgrade as a pre-commit hook by @kurtmckee in #1109
- Simplify the test suite decorators by @kurtmckee in #1113
- Improve coverage config and eliminate unused test suite code by @kurtmckee in #1115
- Build a shared wheel once in the test suite by @kurtmckee in #1114
- Bump actions/checkout from 5 to 6 by @dependabot[bot] in #1122
- Thoroughly test type annotations, and resolve errors by @kurtmckee in #1112
- Fix leeway value in usage documentation by @Matthew1471 in #1124
- Bump actions/download-artifact from 6 to 7 by @dependabot[bot] in #1125
New Contributors
- @shuhaib-aot made their first contribution in #1026
- @qqii made their first contribution in #1056
- @khvn26 made their first contribution in #1048
- @kuldeepkhatke made their first contribution in #1064
- @nikitagashkov made their first contribution in #1068
- @fobiasmog made their first contribution in #1046
- @Matthew1471 made their first contribution in #1124
Full Changelog: jpadilla/pyjwt@2.10.1...2.11.0
Configuration
📅 Schedule: Branch creation - "" (UTC), 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 these updates again.
- If you want to rebase/retry this PR, check this box
This PR has been generated by Renovate Bot.