Skip to content

fix: replace assert with raise ValueError in convertors#3160

Closed
omnislash157 wants to merge 1 commit intoKludex:mainfrom
omnislash157:fix/assert-to-raise-convertors
Closed

fix: replace assert with raise ValueError in convertors#3160
omnislash157 wants to merge 1 commit intoKludex:mainfrom
omnislash157:fix/assert-to-raise-convertors

Conversation

@omnislash157
Copy link

Problem

assert statements in convertors.py are used as runtime input validation, but assert is stripped by python -O. When running with optimization enabled, invalid values are silently accepted instead of being rejected.

I ran into this while auditing ASGI apps that run with python -O in production (common in Docker deployments for performance). The validation just disappears.

What breaks under python -O

I wrote a small test to confirm each one:

Convertor Input Normal mode -O mode
StringConvertor.to_string() "admin/../../etc/passwd" AssertionError: May not contain path separators Silently accepted
StringConvertor.to_string() "" AssertionError: Must not be empty Silently accepted
IntegerConvertor.to_string() -1 AssertionError: Negative integers are not supported Silently accepted
FloatConvertor.to_string() float('nan') AssertionError: NaN values are not supported Silently accepted
FloatConvertor.to_string() float('inf') AssertionError: Infinite values are not supported Silently accepted
FloatConvertor.to_string() -1.0 AssertionError: Negative floats are not supported Silently accepted

The path separator one is the scariest — "admin/../../etc/passwd" passes straight through the convertor if any downstream code uses it to build a file path.

Proof of concept

# test_bypass.py — run with: python test_bypass.py && python -O test_bypass.py
from starlette.convertors import StringConvertor, FloatConvertor, IntegerConvertor

c = StringConvertor()
try:
    c.to_string("admin/../../etc/passwd")
    print(f"BYPASS — path separator accepted (python -O: {not __debug__})")
except (AssertionError, ValueError) as e:
    print(f"GUARDED — {e}")

f = FloatConvertor()
try:
    f.to_string(float('nan'))
    print(f"BYPASS — NaN accepted (python -O: {not __debug__})")
except (AssertionError, ValueError) as e:
    print(f"GUARDED — {e}")
$ python test_bypass.py
GUARDED — May not contain path separators
GUARDED — NaN values are not supported

$ python -O test_bypass.py
BYPASS — path separator accepted (python -O: True)
BYPASS — NaN accepted (python -O: True)

What changed

  • assert "/" not in valueif "/" in value: raise ValueError(...)
  • assert valueif not value: raise ValueError(...)
  • assert value >= 0if value < 0: raise ValueError(...)
  • assert not math.isnan(value)if math.isnan(value): raise ValueError(...)
  • assert not math.isinf(value)if math.isinf(value): raise ValueError(...)

Same error messages, same behavior when running without -O. The only difference is that validation now works regardless of optimization level.

Test plan

  • Existing test suite passes: pytest tests/test_convertors.py (8/8 passed)
  • No behavior change when running without -O
  • Validation now works correctly with -O

Assert statements used as runtime guards are stripped by `python -O`,
silently removing input validation. Under optimization, invalid values
like NaN, Infinity, path separators, and negative numbers are silently
accepted into URL parameters instead of being rejected.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@Kludex Kludex closed this Feb 25, 2026
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.

2 participants