Hidden Assumptions in starlette

12 assumptions this code never checks · 4 critical · spanning Domain, Contract, Temporal, Resource, Environment, Ordering, Scale

Every codebase relies on things it never checks. Most of them are routine. CodeSea looked at kludex/starlette and picked out the few most likely to cause trouble. The full list is just below.

Most of what this code assumes is routine. These 3 are the ones most likely to cause trouble here. The rest are minor; they're under "Show everything".

Worth your attention first

Large file uploads or streaming data will cause memory exhaustion and crash the server. A 1GB file upload will consume 1GB+ RAM per concurrent request.

Worth your attention first

If a handler returns None, dict, or string instead of Response, the framework will crash with AttributeError when trying to serialize the return value to ASGI messages.

Worth your attention first

If any background task raises an exception, all subsequent tasks in the list are skipped silently. Critical cleanup tasks (database connections, file handles) may never execute.

Show everything (9 more)
Resource

Multipart form fields fit in memory as bytearray objects. Each MultipartPart stores field data in a bytearray that grows as data arrives.

If this fails: Large multipart fields (like base64-encoded files submitted as form data) will consume unbounded memory. A 100MB base64 field will use ~133MB RAM per concurrent upload.

starlette/formparsers.py:MultipartPart.data
Environment

Environment variables are only set during application startup before any values are read. The _has_been_read tracking prevents modification of already-accessed env vars.

If this fails: Runtime configuration updates fail silently or crash with EnvironError. Hot configuration reloads in production become impossible once the app has started reading config values.

starlette/config.py:Environ.__setitem__
Ordering

Middleware order in the sequence parameter determines execution order. The framework doesn't validate or document that ServerErrorMiddleware must be outermost and ExceptionMiddleware innermost.

If this fails: If users place their own middleware outside ServerErrorMiddleware, unhandled exceptions won't be caught properly. Request/response modifications by incorrectly ordered middleware can bypass security checks.

starlette/applications.py:Starlette.__init__
Contract

allow_origin_regex parameter contains valid regex syntax. The code compiles it with re.compile(allow_origin_regex) without validation.

If this fails: Invalid regex patterns cause the application to crash at startup with re.error. The error message doesn't clearly indicate it's from CORS configuration, making debugging difficult.

starlette/middleware/cors.py:CORSMiddleware.__init__
Scale

max_age=600 seconds is appropriate for all use cases. The default 10-minute preflight cache duration is hardcoded without considering varying security requirements.

If this fails: High-security applications may cache preflight responses too long, while high-traffic APIs may generate excessive preflight requests due to short cache duration. No guidance provided for tuning this value.

starlette/middleware/cors.py:max_age
Domain

HTTP method names are always uppercase strings matching exact method names ('GET', 'POST', etc). The code checks for lowercase method names as attributes but stores uppercase strings.

If this fails: Custom HTTP methods or case-sensitive method handling won't work correctly. Non-standard methods like 'PATCH' variants or WebDAV methods ('PROPFIND') are silently ignored.

starlette/endpoints.py:HTTPEndpoint._allowed_methods
Resource

is_async_callable() correctly detects async functions and that threadpool execution is always available. No fallback handling if threadpool is unavailable.

If this fails: If threadpool is exhausted or unavailable, sync background tasks will block the event loop indefinitely. Incorrect async detection could cause sync functions to be awaited directly.

starlette/background.py:BackgroundTask.is_async
Contract

Exception handlers are callable objects that accept (request, exc) parameters and return Response objects. No signature validation is performed when adding handlers.

If this fails: Invalid exception handlers cause crashes when exceptions occur rather than at registration time. A handler that expects different parameters will fail with TypeError during request processing.

starlette/middleware/exceptions.py:ExceptionMiddleware.add_exception_handler
Temporal

Environment variable access patterns are single-threaded or that the _has_been_read set is thread-safe. The tracking uses a plain Python set without locking.

If this fails: In multi-threaded scenarios (like with threading-based ASGI servers), race conditions can occur where env vars appear unread when they were actually read by another thread, leading to inconsistent EnvironError exceptions.

starlette/config.py:Environ._has_been_read

See the full structural analysis of starlette: the pipeline, data models, and system behavior that put these assumptions in context.

Full analysis of kludex/starlette →

Frequently Asked Questions

What does starlette assume that could break in production?

The one most likely to cause trouble: Request bodies can be fully loaded into memory without causing OOM. The _CachedRequest class reads entire request body via self._wrapped_rc_stream = self.stream() and caches it in memory. If this fails, Large file uploads or streaming data will cause memory exhaustion and crash the server. A 1GB file upload will consume 1GB+ RAM per concurrent request.

How many hidden assumptions does starlette have?

CodeSea found 12 assumptions starlette relies on but never validates, 4 of them critical, spanning Domain, Contract, Temporal, Resource, Environment, Ordering, Scale. Most are routine — the analysis flags the two or three most likely to actually bite.

What is a hidden assumption?

Something the code depends on but never checks: a data shape, an ordering, an environment condition, a scale limit, or a contract with another service. It holds until the world it runs in changes, then fails silently.