Releases: jpadilla/pyjwt
2.13.0
PyJWT 2.13.0 — Security Release
This release bundles five security fixes plus three additional hardening / spec-compliance changes. We recommend all users upgrade.
Security
-
GHSA-xgmm-8j9v-c9wx— JWK JSON accepted as HMAC secret (algorithm confusion).HMACAlgorithm.prepare_keypreviously rejected PEM- and SSH-formatted asymmetric keys but did not catch a JWK passed as a raw JSON string. In a verifier configured with both symmetric and asymmetric algorithms inalgorithms=[…]and a raw-JSON JWK as the key, an attacker could forge HS256 tokens using the JWK text as the HMAC secret. The guard has been extended to reject any JWK-shaped JSON. Reported by @aradona91. -
GHSA-jq35-7prp-9v3f— Algorithm allow-list bypass withPyJWK/PyJWKClient. When verifying with aPyJWK, the caller'salgorithms=[…]allow-list was checked against the token headeralgas a string only; actual verification used the algorithm bound to thePyJWK. An attacker who controlled a registered JWKS key could sign with one algorithm and advertise another on the header. PyJWT now requires the token headeralgto match thePyJWK's algorithm before verification. Reported by @sushi-gif. -
GHSA-w7vc-732c-9m39— DoS via base64 decode of unused payload segment whenb64=false. For detached-payload JWS (b64=false), the compact-form payload segment was base64-decoded before being discarded in favor of the caller-supplieddetached_payload. An attacker could inflate the unused segment to force CPU + memory cost without holding a valid signature. The segment is now required to be empty per RFC 7515 Appendix F, and is no longer decoded. Reported by @thesmartshadow. -
GHSA-993g-76c3-p5m4—PyJWKClientaccepts non-HTTP(S) URIs.PyJWKClient.fetch_datapassed its URI tourllib.request.urlopen, which by default also handlesfile://,ftp://, anddata:schemes. An application that fed an attacker-influenced URI intoPyJWKClientcould be coerced into reading local files or reaching other unintended schemes.PyJWKClientnow rejects any URI whose scheme isn'thttporhttps. Reported by @KEIJOT. -
GHSA-fhv5-28vv-h8m8—PyJWKClientcache wiped on fetch error. Afinally-blockput(jwk_set=None)cleared the JWK Set cache whenever a fetch raised, turning a transient JWKS-endpoint outage into application-wide auth failure. The cache write was moved into the success path; transient errors no longer evict valid cached keys. Reported by @eddieran.
Fixed
- Reject empty HMAC keys outright in
HMACAlgorithm.prepare_keywithInvalidKeyErrorinstead of accepting them with only a warning. Defends against theos.getenv("JWT_SECRET", "")footgun. Thanks to @SnailSploit and @spartan8806 for the reports. - Forward per-call
options(includingenforce_minimum_key_length) fromPyJWT.decodethrough toPyJWS._verify_signature. The option was previously silently dropped between the two layers, so it only took effect when set on thePyJWTinstance. Thanks to @WLUB for the report. - RFC 7797 §3 compliance for
b64=false: the encoder now auto-adds"b64"tocrit, and the decoder rejects tokens that setb64=falsewithout listing it incrit. Thanks to @MachineLearning-Nerd for the report.
Changed
- Migrate the
dev,docs, andtestspackage extras to dependency groups, by @kurtmckee in #1152.
Upgrade notes
Most fixes are invisible to correctly-configured callers. A few behavioral changes you may encounter:
- Empty HMAC keys now raise. If your app passed
""orb""as a secret (often via a missing env var, e.g.os.getenv("JWT_SECRET", "")),encode/decodewill now raiseInvalidKeyError. This is the intended behavior — fix the configuration. PyJWKdecoding now requires the token'salgto match the JWK's algorithm. Previously a mismatch was silently honored if the headeralgappeared in the allow-list. Tokens that relied on this mismatch will now fail withInvalidAlgorithmError.PyJWKClientnow rejects non-HTTP(S) URIs at construction time. Tests or dev environments that fetched JWKS fromfile://URIs need to switch to a local HTTP server or load the JWKS by other means (e.g. constructPyJWKSet.from_dict(...)directly).b64=falsetokens are now strictly RFC 7515 / 7797 compliant. Tokens with a non-empty compact-form payload segment, or that omit"b64"fromcrit, will be rejected. PyJWT-produced tokens always satisfy both invariants, so round-trips through PyJWT are unaffected.enforce_minimum_key_lengthset per-call now takes effect. Callers who passedoptions={"enforce_minimum_key_length": True}tojwt.decode()previously got no enforcement; they will now getInvalidKeyErroron undersized keys, as documented.
Full changelog: 2.12.1...2.13.0
2.12.1
2.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: 2.11.0...2.12.0
2.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: 2.10.1...2.11.0
2.10.1
Fixed
- Prevent partial matching of
issclaim. Thanks @fabianbadoi! (See: GHSA-75c5-xw7c-p5pm)
Full Changelog: 2.10.0...2.10.1
2.10.0
What's Changed
- chore: use sequence for typing rather than list by @imnotjames in #970
- Add support for Python 3.13 by @hugovk in #972
- [pre-commit.ci] pre-commit autoupdate by @pre-commit-ci in #971
- Add an RTD config file to resolve RTD build failures by @kurtmckee in #977
- docs: Update
iatexception docs by @pachewise in #974 - Remove algorithm requirement for JWT API by @luhn in #975
- [pre-commit.ci] pre-commit autoupdate by @pre-commit-ci in #978
- Create SECURITY.md by @auvipy in #973
- docs fix: decode_complete scope and algorithms by @RbnRncn in #982
- fix doctest for docs/usage.rst by @pachewise in #986
- fix test_utils.py not to xfail by @pachewise in #987
- Correct jwt.decode audience param doc expression by @peter279k in #994
- Add PS256 encoding and decoding usage by @peter279k in #992
- Add API docs for PyJWK by @luhn in #980
- Refactor project configuration files from setup.cfg to pyproject.toml PEP-518 by @cleder in #995
- Add JWK support to JWT encode by @luhn in #979
- Update pre-commit hooks to lint pyproject.toml by @cleder in #1002
- Add EdDSA algorithm encoding/decoding usage by @peter279k in #993
- Ruff linter and formatter changes by @gagandeepp in #1001
- Validate
subandjticlaims for the token by @Divan009 in #1005 - Add ES256 usage by @Gautam-Hegde in #1003
- Encode EC keys with a fixed bit length by @way-dave in #990
- [pre-commit.ci] pre-commit autoupdate by @pre-commit-ci in #1000
- Drop support for Python 3.8 by @kkirsche in #1007
- Prepare 2.10.0 release by @benvdh in #1011
- Bump codecov/codecov-action from 4 to 5 by @dependabot in #1014
- [pre-commit.ci] pre-commit autoupdate by @pre-commit-ci in #1006
New Contributors
- @imnotjames made their first contribution in #970
- @kurtmckee made their first contribution in #977
- @pachewise made their first contribution in #974
- @RbnRncn made their first contribution in #982
- @peter279k made their first contribution in #994
- @cleder made their first contribution in #995
- @gagandeepp made their first contribution in #1001
- @Divan009 made their first contribution in #1005
- @Gautam-Hegde made their first contribution in #1003
- @way-dave made their first contribution in #990
Full Changelog: 2.9.0...2.10.0
2.9.0
What's Changed
- [pre-commit.ci] pre-commit autoupdate by @pre-commit-ci in #905
- [pre-commit.ci] pre-commit autoupdate by @pre-commit-ci in #909
- Add support for Python 3.12 by @hugovk in #910
- [pre-commit.ci] pre-commit autoupdate by @pre-commit-ci in #911
- Fix an unnecessary str concat by @sirosen in #904
- Update jwt-api to accept either a string or list of strings for issuer validation by @mattpollak in #913
- Bump actions/checkout from 3 to 4 by @dependabot in #916
- [pre-commit.ci] pre-commit autoupdate by @pre-commit-ci in #917
- [pre-commit.ci] pre-commit autoupdate by @pre-commit-ci in #922
- [pre-commit.ci] pre-commit autoupdate by @pre-commit-ci in #926
- Bump actions/setup-python from 4 to 5 by @dependabot in #931
- Bump hynek/build-and-inspect-python-package from 1 to 2 by @dependabot in #935
- docs/api: document strict_aud on decode_complete by @woodruffw in #923
- chore: fix docs step by @jpadilla in #950
- [pre-commit.ci] pre-commit autoupdate by @pre-commit-ci in #953
- Add coverage and improve performance of is_ssh_key by @bdraco in #940
- Decode with PyJWK by @luhn in #886
- Remove an unused variable from an example code block by @kenkoooo in #958
- Handle load_pem_public_key ValueError by @CollinEMac in #952
- [pre-commit.ci] pre-commit autoupdate by @pre-commit-ci in #960
- Raise exception when required cryptography dependency is missing by @tobloef in #963
- [pre-commit.ci] pre-commit autoupdate by @pre-commit-ci in #965
- Add 2.9.0 changelog. Fixes #949 by @benvdh in #967
New Contributors
- @mattpollak made their first contribution in #913
- @bdraco made their first contribution in #940
- @luhn made their first contribution in #886
- @kenkoooo made their first contribution in #958
- @CollinEMac made their first contribution in #952
- @tobloef made their first contribution in #963
- @benvdh made their first contribution in #967
Full Changelog: 2.8.0...2.9.0
2.8.0
What's Changed
- Export PyJWKClientConnectionError class by @daviddavis in #887
- [pre-commit.ci] pre-commit autoupdate by @pre-commit-ci in #889
- Patch 1 by @juur in #891
- [pre-commit.ci] pre-commit autoupdate by @pre-commit-ci in #896
- [pre-commit.ci] pre-commit autoupdate by @pre-commit-ci in #898
- [pre-commit.ci] pre-commit autoupdate by @pre-commit-ci in #900
- Update python version by @auvipy in #895
- api_jwt: add a
strict_audoption by @woodruffw in #902
New Contributors
Full Changelog: 2.7.0...2.8.0
2.7.0
What's Changed
- Add classifier for Python 3.11 by @eseifert in #818
- Add
Algorithm.compute_hash_digestand use it to implement at_hash validation example by @sirosen in #775 - fix: use datetime.datetime.timestamp function to have a milliseconds by @daillouf in #821
- [pre-commit.ci] pre-commit autoupdate by @pre-commit-ci in #825
- Custom header configuration in jwk client by @thundercat1 in #823
- [pre-commit.ci] pre-commit autoupdate by @pre-commit-ci in #828
- [pre-commit.ci] pre-commit autoupdate by @pre-commit-ci in #833
- [pre-commit.ci] pre-commit autoupdate by @pre-commit-ci in #835
- Add PyJWT._{de,en}code_payload hooks by @akx in #829
- Add
sort_headersparameter toapi_jwt.encodeby @evroon in #832 - Make mypy configuration stricter and improve typing by @akx in #830
- Bump actions/stale from 6 to 7 by @dependabot in #840
- [pre-commit.ci] pre-commit autoupdate by @pre-commit-ci in #838
- Add more types by @Viicos in #843
- Differentiate between two errors by @irdkwmnsb in #809
- Fix
_validate_iatvalidation by @Viicos in #847 - Improve error messages when cryptography isn't installed by @Viicos in #846
- [pre-commit.ci] pre-commit autoupdate by @pre-commit-ci in #852
- [pre-commit.ci] pre-commit autoupdate by @pre-commit-ci in #855
- [pre-commit.ci] pre-commit autoupdate by @pre-commit-ci in #859
- Make
Algorithman abstract base class by @Viicos in #845 - docs: correct mistake in the changelog about verify param by @gbillig in #866
- [pre-commit.ci] pre-commit autoupdate by @pre-commit-ci in #868
- Bump actions/stale from 7 to 8 by @dependabot in #872
- [pre-commit.ci] pre-commit autoupdate by @pre-commit-ci in #874
- Add a timeout for PyJWKClient requests by @daviddavis in #875
- Add client connection error exception by @daviddavis in #876
- Add complete types to take all allowed keys into account by @Viicos in #873
- [pre-commit.ci] pre-commit autoupdate by @pre-commit-ci in #878
- Build and upload PyPI package by @jpadilla in #884
- Fix for issue #862 - ignore invalid keys in a jwks. by @timw6n in #863
- Add
as_dictoption toAlgorithm.to_jwkby @fluxth in #881
New Contributors
- @eseifert made their first contribution in #818
- @daillouf made their first contribution in #821
- @thundercat1 made their first contribution in #823
- @evroon made their first contribution in #832
- @Viicos made their first contribution in #843
- @irdkwmnsb made their first contribution in #809
- @gbillig made their first contribution in #866
- @daviddavis made their first contribution in #875
- @timw6n made their first contribution in #863
- @fluxth made their first contribution in #881
Full Changelog: 2.6.0...2.7.0
2.6.0
What's Changed
- fix: version 2.5.0 heading typo by @c0state in #803
- Remove
types-cryptographyfromcryptoextra by @lautat in #805 - bump up cryptography >= 3.4.0 by @jpadilla in #807
- [pre-commit.ci] pre-commit autoupdate by @pre-commit-ci in #798
- Bump actions/stale from 5 to 6 by @dependabot in #808
- Invalidate exp when exp == now() by @wcedmisten-reify in #797
- Handling 'ImmatureSignatureError' for issued_at time by @sriharan16 in #794
- [pre-commit.ci] pre-commit autoupdate by @pre-commit-ci in #810
- Bump version to 2.6.0 by @jpadilla in #813
New Contributors
- @c0state made their first contribution in #803
- @lautat made their first contribution in #805
- @wcedmisten-reify made their first contribution in #797
- @sriharan16 made their first contribution in #794
Full Changelog: 2.5.0...2.6.0