Skip to content

fix: reject NaN in numeric comparison keywords (minimum, maximum, exclusiveMinimum, exclusiveMaximum)#1506

Open
gaoflow wants to merge 2 commits into
python-jsonschema:mainfrom
gaoflow:main
Open

fix: reject NaN in numeric comparison keywords (minimum, maximum, exclusiveMinimum, exclusiveMaximum)#1506
gaoflow wants to merge 2 commits into
python-jsonschema:mainfrom
gaoflow:main

Conversation

@gaoflow

@gaoflow gaoflow commented Jun 24, 2026

Copy link
Copy Markdown

NaN (float("nan")) currently passes all numeric comparison checks because Python's NaN comparisons always return False. This has two consequences:

  1. Schema validation bypass: check_schema does not reject schemas containing NaN in numeric fields. For example, {"multipleOf": float("nan")} passes schema validation because the meta-schema's exclusiveMinimum: 0 fails to reject NaN (NaN <= 0 is False). This then causes validate() to crash with ValueError: cannot convert float NaN to integer inside the multipleOf handler.

  2. Instance validation bypass: Instances containing NaN pass any minimum/maximum/exclusiveMinimum/exclusiveMaximum constraint, even though NaN is not a valid JSON number and cannot satisfy any ordering relationship.

The fix inverts the comparison logic to use the positive (non-negated) form:

  • not (instance > minimum) instead of instance <= minimum
  • not (instance < maximum) instead of instance >= maximum
  • not (instance >= minimum) instead of instance < minimum
  • not (instance <= maximum) instead of instance > maximum

For normal numbers the result is identical. For NaN, all four positive comparisons return False, so the negation correctly yields True (rejecting NaN). The same fix is applied to the legacy draft3/draft4 equivalents in _legacy_keywords.py.

gaoflow and others added 2 commits June 24, 2026 12:39
…lusiveMinimum, exclusiveMaximum)

NaN (float('nan')) previously passed all numeric comparison checks
because Python's NaN comparisons always return False:
  instance < minimum   -> False (no error, should fail)
  instance > maximum   -> False (no error, should fail)
  instance <= minimum  -> False (no error, should fail)
  instance >= maximum  -> False (no error, should fail)

This allowed NaN-infested schemas to pass check_schema (e.g.
multipleOf=NaN, since the exclusiveMinimum:0 in the meta-schema
did not reject NaN). validate() then crashed with ValueError
inside the multipleOf handler when it tried int(quotient) on NaN.

Invert the comparisons to use the positive form:
  not (instance > minimum)  for exclusiveMinimum
  not (instance < maximum)  for exclusiveMaximum
  not (instance >= minimum) for minimum
  not (instance <= maximum) for maximum

For non-NaN numbers the result is identical.  For NaN, all four
positive forms return False, so 'not' yields True (correctly
rejecting NaN).

Also fix the legacy draft3/draft4 equivalents in _legacy_keywords.py.
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.

1 participant