Skip to content

Reject out-of-range exp/nbf/iat NumericDate claim values - #1191

Open
pctablet505 wants to merge 3 commits into
jpadilla:masterfrom
pctablet505:fix/numeric-date-range-check
Open

pctablet505 wants to merge 3 commits into
jpadilla:masterfrom
pctablet505:fix/numeric-date-range-check

Conversation

@pctablet505

Copy link
Copy Markdown

Summary

exp, nbf, and iat are validated with a plain int(payload[claim]) comparison. Python integers are arbitrary precision, so a claim like 10**50 sails through the comparison even though it can't represent a real date:

>>> import jwt
>>> tok = jwt.encode({"exp": 10**50}, "secret", algorithm="HS256")
>>> jwt.decode(tok, "secret", algorithms=["HS256"])
{'exp': 100000000000000000000000000000000000000000000000000}

RFC 7519 defines these claims as NumericDate values — seconds since the epoch representing an actual date/time. A value like this doesn't represent one, and code that reasonably converts the accepted claim with datetime.fromtimestamp() hits an uncaught OverflowError.

Fix

Add a bound matching the range datetime can represent (up to year 9999, i.e. 253402300799) and reject exp/nbf/iat values outside it with the same exception types these validators already raise for other malformed values (DecodeError for exp/nbf, InvalidIssuedAtError for iat). No behavior changes for any value in the range real tokens use.

Closes #1171.

Testing

Full suite passes locally (378 tests incl. 9 new, 0 skipped with cryptography installed): pytest tests/. ruff check, ruff format --check, and mypy jwt/api_jwt.py are clean.

RFC 7519 defines exp, nbf, and iat as NumericDate values representing
an actual UTC date/time. Python's arbitrary-precision integers let a
claim like 10**50 pass the plain integer comparisons in _validate_exp,
_validate_nbf, and _validate_iat, even though the value can't
represent a real date. Applications that reasonably convert an
accepted claim with datetime.fromtimestamp() then hit an uncaught
OverflowError.

Add a bound matching the range datetime can represent (up to year
9999) and reject claims outside it with the existing DecodeError /
InvalidIssuedAtError types, consistent with how these validators
already report other malformed values.

Fixes jpadilla#1171
A claim serialized as float infinity (e.g. jwt.encode({"exp": float("inf")}))
round-trips through JSON and reaches int(payload[...]) in
_validate_exp/_validate_nbf/_validate_iat, where int(float("inf")) raises an
uncaught OverflowError instead of a JWT exception. Catch OverflowError
alongside the existing ValueError handling and reject with the same
out-of-range DecodeError / InvalidIssuedAtError used for oversized integer
values.

Refs jpadilla#1171
@pctablet505
pctablet505 marked this pull request as ready for review July 24, 2026 16:44

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Expiration Bypass via Oversized exp Claim Value

1 participant