# PYTHON-FLASK-SEC-010: Flask NaN Injection via float()

> **Severity:** LOW | **CWE:** CWE-704 | **OWASP:** A03:2021

- **Language:** Python
- **Category:** Flask
- **URL:** https://codepathfinder.dev/registry/python/flask/PYTHON-FLASK-SEC-010
- **Detection:** `pathfinder scan --ruleset python/PYTHON-FLASK-SEC-010 --project .`

## Description

This rule detects NaN (Not a Number) injection in Flask applications where user-
controlled input from HTTP request parameters flows to Python's float() built-in
without subsequent validation for non-finite values. Python's float() accepts the
special string literals "nan", "inf", "-inf", "infinity", and "+infinity"
(case-insensitive) and converts them to the IEEE 754 special float values NaN and
Infinity.

These special float values propagate through arithmetic in unexpected ways: NaN
comparisons always return False (NaN != NaN, NaN < 0 is False, NaN > 0 is False),
Inf causes overflow in operations expecting bounded values, and NaN serialized to
JSON via Python's json module produces invalid JSON (json.dumps({'x': float('nan')})
outputs {"x": NaN} which is not valid JSON and may cause downstream parsing failures
or type confusion in JavaScript consumers).

The rule traces tainted strings from Flask request sources to the float() call at
argument position 0. Using int() on the result is recognized as a sanitizer because
int(float('nan')) raises ValueError, effectively rejecting NaN. The correct fix is
to validate the result of float() with math.isnan() and math.isinf() before using
the value in calculations, storage, or serialization.


## Vulnerable Code

```python
from flask import Flask, request

app = Flask(__name__)

@app.route('/convert')
def convert():
    value = request.args.get('value')
    result = float(value)
    return str(result)
```

## Secure Code

```python
from flask import Flask, request, abort
import math

app = Flask(__name__)

@app.route('/calculate')
def calculate():
    value_str = request.args.get('value', '')
    try:
        value = float(value_str)
    except (ValueError, TypeError):
        abort(400, 'Invalid number')

    # SAFE: Reject NaN and Inf before using the value
    if math.isnan(value) or math.isinf(value):
        abort(400, 'Value must be a finite number')

    result = value * 2.5
    return {'result': result}

@app.route('/price')
def get_price():
    price_str = request.args.get('price', '')
    try:
        price = float(price_str)
    except (ValueError, TypeError):
        abort(400, 'Invalid price')

    # SAFE: int() raises ValueError for NaN and Inf
    try:
        price_cents = int(price * 100)
    except (ValueError, OverflowError):
        abort(400, 'Price out of range')

    return {'price_cents': price_cents}

```

## Detection Rule (Python SDK)

```python
from rules.python_decorators import python_rule
from codepathfinder import calls, flows, QueryType
from codepathfinder.presets import PropagationPresets

class Builtins(QueryType):
    fqns = ["builtins"]


@python_rule(
    id="PYTHON-FLASK-SEC-010",
    name="Flask NaN Injection",
    severity="LOW",
    category="flask",
    cwe="CWE-704",
    tags="python,flask,nan-injection,type-confusion,CWE-704",
    message="User input flows to float() which may produce NaN/Inf. Validate numeric input.",
    owasp="A03:2021",
)
def detect_flask_nan_injection():
    """Detects Flask request data flowing to float() conversion."""
    return flows(
        from_sources=[
            calls("request.args.get"),
            calls("request.form.get"),
            calls("request.values.get"),
        ],
        to_sinks=[
            Builtins.method("float").tracks(0),
            calls("float"),
        ],
        sanitized_by=[
            calls("int"),
        ],
        propagates_through=PropagationPresets.standard(),
        scope="global",
    )
```

## How to Fix

- After calling float() on user input, always validate the result with math.isfinite() (which rejects both NaN and Inf in a single call) before using the value in calculations or storage.
- Use int() conversion after float() when integer precision is required -- int() raises ValueError for NaN and OverflowError for Inf, both of which reject the problematic values.
- When serializing numeric values to JSON, use a custom JSON encoder that raises an error on non-finite floats rather than producing invalid JSON output.
- Validate numeric bounds (minimum and maximum acceptable values) in addition to finiteness -- this catches values that are technically finite but outside valid business ranges.
- Consider using Pydantic or marshmallow for request data validation -- both libraries reject NaN and Inf by default when validating float fields.

## Security Implications

- **Invalid JSON Output Causing API Client Failures:** Python's json module serializes float('nan') as NaN and float('inf') as Infinity,
both of which are not valid JSON (RFC 8259). API clients that use strict JSON
parsers throw exceptions on these values. An attacker who can supply "nan" as a
numeric parameter can cause downstream service failures by forcing invalid JSON
responses from the Flask application.

- **Arithmetic Logic Bypass via NaN Comparisons:** NaN comparisons always return False in Python. Code like
if price < 0: raise ValueError fails silently when price is NaN because
NaN < 0 is False and NaN >= 0 is also False. An attacker who injects NaN
into a price or quantity field may bypass validation logic that relies on
numeric comparisons, potentially submitting orders with NaN prices to
financial systems.

- **Database and ORM Type Errors:** Storing NaN or Inf in database columns typed as NUMERIC, DECIMAL, or FLOAT
causes constraint violations in strict databases (PostgreSQL rejects NaN in
some contexts) or silent corruption in lenient ones. This can cause cascading
failures in reporting, aggregation queries, and data exports.

- **Denial of Service via Propagating NaN:** NaN and Inf propagate silently through calculations. A NaN that enters a
statistical computation (average, standard deviation, percentile) corrupts
the entire result without raising an exception, potentially causing the
application to return None or crash in downstream code that does not expect
these values.


## FAQ

**Q: Why is this severity LOW when it can bypass validation logic?**

NaN injection is LOW severity because it requires specific downstream code patterns
to be exploitable (comparisons that assume finite values, JSON serialization without
validation). It is not directly exploitable for code execution or data theft in most
cases. However, it is worth fixing because the bugs it enables -- silent validation
bypasses, invalid JSON output, database errors -- are subtle and hard to diagnose.


**Q: How does NaN bypass comparison-based validation?**

In Python, any comparison involving NaN returns False: float('nan') > 0 is False,
float('nan') < 0 is False, float('nan') == 0 is False, float('nan') != 0 is True.
Code like if value < 0: raise ValueError('negative not allowed') silently accepts
NaN because NaN < 0 is False. The validator logic assumes finite values.


**Q: Does math.isfinite() replace both math.isnan() and math.isinf()?**

Yes. math.isfinite(x) returns True only if x is a regular finite number (not NaN,
not +Inf, not -Inf). It is equivalent to not math.isnan(x) and not math.isinf(x)
in a single call. Use math.isfinite() as the primary validation check.


**Q: What happens when float('nan') is stored in a PostgreSQL NUMERIC column?**

PostgreSQL's NUMERIC and DECIMAL types do accept NaN (it represents "not a number"
in SQL numeric contexts). However, NaN in a NUMERIC column causes unexpected behavior
in aggregate functions (SUM, AVG return NaN), ORDER BY comparisons (NaN sorts
inconsistently), and application code that reads the value and applies comparisons.
FLOAT/DOUBLE columns in PostgreSQL also accept NaN. Prevent storage of NaN with
a CHECK constraint or application-level validation.


**Q: Does this rule fire on float() calls that already validate input elsewhere?**

The rule fires on tainted flows to float() at position 0. If the input is validated
against an allowlist or pattern before the taint reaches float(), and the analysis
recognizes that validation as sanitizing, no finding is reported. If the validation
happens after float() (e.g., check math.isfinite after converting), the finding still
fires because the risky conversion happens before validation -- though the code may
be correct, adding the check after conversion is the right pattern.


**Q: Can I fix this by catching ValueError from float()?**

Catching ValueError from float() handles invalid strings (e.g., "abc") but does not
help with NaN and Inf because float('nan') and float('inf') do not raise ValueError --
they succeed and return the special float values. You need an explicit check with
math.isfinite() after the float() call.


**Q: We use Pydantic for request validation. Does Pydantic reject NaN?**

Pydantic v2 with strict mode rejects NaN and Inf for float fields by default.
Pydantic v1 allows NaN in float fields unless you add a validator. If you use
Pydantic for request parsing, verify your model's behavior by passing "nan" as
a float field value in your tests. If the model accepts it, add a
@validator that calls math.isfinite().


## References

- [CWE-704: Incorrect Type Conversion or Cast](https://cwe.mitre.org/data/definitions/704.html)
- [Python float() documentation and special values](https://docs.python.org/3/library/functions.html#float)
- [Python math.isnan and math.isinf documentation](https://docs.python.org/3/library/math.html#math.isnan)
- [IEEE 754 NaN propagation rules](https://en.wikipedia.org/wiki/NaN#Operations_generating_NaN)
- [Python json module and non-finite float handling](https://docs.python.org/3/library/json.html#json.dumps)
- [OWASP Input Validation Cheat Sheet](https://cheatsheetseries.owasp.org/cheatsheets/Input_Validation_Cheat_Sheet.html)

---

Source: https://codepathfinder.dev/registry/python/flask/PYTHON-FLASK-SEC-010
Code Pathfinder — Open source, type-aware SAST with cross-file dataflow analysis
