Repository navigation
fix(jwt): exp boundary, and reject NaN and Infinity in time claims - #109
Merged
Merged
Conversation
RFC 7519 section 4.1.4: exp is the time on or after which the JWT MUST NOT be accepted. validate_exp raised only for exp < now - leeway, so a token was still accepted during the second in which exp == now. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
NumericDate (RFC 7519 section 2) is a JSON number, and NaN and Infinity are not. Python's json reads them anyway, and every comparison with NaN is false, so a token carrying "exp": NaN was never expired, and neither was "exp": Infinity. nbf and iat accepted NaN the same way. _validate_numeric_time now accepts an int, or a float that is finite. An int is always finite, and math.isfinite raises OverflowError for one too large for a float, so ints do not go through it. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Codecov Report✅ All modified and coverable lines are covered by tests. Additional details and impacted files@@ Coverage Diff @@
## main #109 +/- ##
=======================================
Coverage 99.86% 99.86%
=======================================
Files 47 47
Lines 3014 3015 +1
Branches 362 362
=======================================
+ Hits 3010 3011 +1
Misses 2 2
Partials 2 2
Flags with carried forward coverage won't be shown. Click here to find out more. ☔ View full report in Codecov by Harness. 🚀 New features to boost your workflow:
|
lepture
approved these changes
Nov 3, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Two fixes to the time claims in
JWTClaimsRegistry, one commit each, so they can be split if you prefer.1.
exp == now. RFC 7519 §4.1.4 definesexpas the time "on or after which the JWT MUST NOT be accepted for processing", as the docstring ofvalidate_expsays. The check raised only whenexp < now - leeway, so a token was still accepted during the second in whichexp == now(orexp == now - leewaywith a leeway).<becomes<=.nbfandiatalready follow the RFC. The effect is at most one extra second of validity, the same as a leeway one second larger.2. NaN and Infinity. NumericDate (RFC 7519 §2) is a JSON number, and NaN and Infinity are not. Python's
jsonreads them anyway (andjson.dumpswrites them by default), and every comparison with NaN is false, so a token with"exp": NaNwas never expired, and neither was"exp": Infinity.nbfandiataccepted NaN the same way._validate_numeric_timenow takes an int, or a float that is finite. An int is always finite, andmath.isfiniteraisesOverflowErrorfor one too large for a float, so ints don't go through it. PyJWT 2.15.1 rejects such anexpwithDecodeError.The token has to be signed, so this needs an issuer that writes a NaN or an Infinity into a claim, not an attacker. For that reason, and for the one-second window in the first fix, I've sent this as a plain PR rather than through the Security tab.
A non-finite value is now an
InvalidClaimError("must be a NumericDate value"). A value that was already refused only changes its error:"exp": -InfinitywasExpiredTokenError.Tests.
test_validate_expchecks both edges, with and without a leeway.test_validate_time_claims_must_be_finitecoversexp,nbfandiatwith NaN, Infinity and -Infinity.test_decode_then_validate_a_token_that_never_expiresgoes throughjwt.encodeandjwt.decode.test_validate_time_claims_accept_large_integerspasses with and without the fix; it guards against theOverflowErrorabove.On
main(bc06b26)test_validate_exp, the NaN test and the decode test fail, and they pass with the fixes.pytest(300 passed),ruff check,ruff format --checkandmypyare clean.🤖 Generated with Claude Code