How form limit bypass DoS happens in Python Starlette and how to fix it
The Dangerous Illusion of Safety
There's a particularly insidious class of security bug: one where the developer does everything right, sets the correct limits, writes what looks like safe code — and the framework silently ignores them. CVE-2026-54283 is exactly that kind of vulnerability in Starlette, the popular Python ASGI framework that underpins FastAPI.
In this case, any developer who carefully configured request.form(max_fields=100) or request.form(max_files=10) to protect their endpoint from oversized form submissions was unknowingly leaving the door wide open — because Starlette 1.2.1 silently discarded those limits when the request used application/x-www-form-urlencoded encoding.
The Vulnerability Explained
What request.form() Is Supposed to Do
Starlette's request.form() is the standard way to parse incoming HTML form data in ASGI applications. It supports two content types:
multipart/form-data— typically used for file uploadsapplication/x-www-form-urlencoded— the default encoding for most HTML forms (key=value pairs, URL-encoded)
To prevent abuse, Starlette exposes limit parameters on the form parser:
# Developer's intent: limit form submissions to 100 fields
data = await request.form(max_fields=100, max_files=10)
This looks safe. It reads like a correctly hardened endpoint. But in Starlette 1.2.1, the max_fields and max_files limits were only enforced for multipart/form-data requests. For application/x-www-form-urlencoded — the most common form encoding — these limits were silently ignored.
The Attack Scenario
An attacker targeting a Starlette 1.2.1 application doesn't need to upload files or craft multipart payloads. They simply send a standard HTML form POST with an enormous number of fields:
POST /submit HTTP/1.1
Content-Type: application/x-www-form-urlencoded
field1=value1&field2=value2&field3=value3&...&field500000=value500000
Because the application/x-www-form-urlencoded parser never checks the max_fields limit, Starlette will happily parse all 500,000 fields, allocating memory for each key-value pair. With enough concurrent requests, an attacker can:
- Exhaust available server memory, causing OOM kills
- Spike CPU usage from repeated string splitting and URL-decoding
- Starve legitimate requests of resources, causing effective service downtime
This is a classic CWE-400: Uncontrolled Resource Consumption scenario — the application consumes resources proportional to attacker-controlled input with no enforced upper bound.
Why This Is Especially Dangerous
The silent nature of the bypass makes this particularly treacherous. Unlike a misconfiguration where no limit is set at all, here the developer has set a limit. Code reviews would pass. Security audits might not flag it. The application appears hardened. But at runtime, the protection simply doesn't activate for the most common form content type.
The Fix
The remediation is a version upgrade of the starlette package from 1.2.1 to 1.3.1, as reflected in the uv.lock file:
[[package]]
name = "starlette"
-version = "1.2.1"
+version = "1.3.1"
source = { registry = "https://pypi.org/simple" }
The corresponding package hashes also changed, confirming a genuine package swap:
-sdist = { url = "...starlette-1.2.1.tar.gz",
- hash = "sha256:9b9b5ebb992e67d6093741e63c2f59e4f6fff986f81163c087867bd7b924b3f6" }
+sdist = { url = "...starlette-1.3.1.tar.gz",
- hash = "sha256:05d0213193f2fbaae60e2ecb593b4add4262ad4e46536b54abe36f11a71724e0" }
Starlette 1.3.1 fixes the root cause by applying the max_fields and max_files enforcement logic to both form content types — multipart/form-data and application/x-www-form-urlencoded. After this fix, a form submission with 500,000 fields will be rejected at the parser level, before any significant memory allocation occurs.
Before vs. After Behavior
| Scenario | Starlette 1.2.1 | Starlette 1.3.1 |
|---|---|---|
multipart/form-data with 10,000 fields, max_fields=100 |
✅ Rejected | ✅ Rejected |
application/x-www-form-urlencoded with 10,000 fields, max_fields=100 |
❌ Silently accepted | ✅ Rejected |
| No limits configured, any content type | ❌ Unbounded | ❌ Unbounded (use defaults) |
The fix ensures that the developer's intent — expressed through the max_fields and max_files parameters — is actually enforced regardless of how the form data was encoded.
Prevention & Best Practices
1. Always Pin and Audit Dependencies
The vulnerability lived in uv.lock, Starlette's locked dependency entry. Dependency lock files are your source of truth for what actually runs in production — and they must be scanned regularly with tools like Trivy, pip-audit, or Safety.
# Scan your lock file with Trivy
trivy fs uv.lock
# Or use pip-audit for Python projects
pip-audit
2. Set Form Limits — And Verify They Work
After upgrading to Starlette 1.3.1+, form limits now work as documented. Use them:
from starlette.requests import Request
async def handle_form(request: Request):
# These limits are now enforced for ALL content types in 1.3.1+
async with request.form(max_fields=50, max_files=5, max_fields_size=16384) as form:
name = form.get("name")
# process safely
3. Apply Defense-in-Depth at the Infrastructure Layer
Even with correct application-level limits, apply server-level body size restrictions:
- Nginx:
client_max_body_size 1m; - Uvicorn/Gunicorn: Configure request timeout and body size limits
- API Gateway / Load Balancer: Set maximum request body size policies
These server-level controls act as a safety net if application-level limits are ever bypassed or misconfigured.
4. Monitor for Anomalous Form Submissions
Add logging or metrics around form parsing to detect abuse patterns:
import logging
async def handle_form(request: Request):
try:
async with request.form(max_fields=50) as form:
field_count = len(form)
if field_count > 20: # Unusual for your app
logging.warning(f"Large form submission: {field_count} fields from {request.client.host}")
# continue processing
except Exception as e:
logging.error(f"Form parsing rejected: {e}")
raise
5. Reference Standards
- CWE-400: Uncontrolled Resource Consumption — the root class of this vulnerability
- OWASP: Denial of Service Cheat Sheet
- OWASP Top 10: A05:2021 — Security Misconfiguration (trusting framework defaults without verification)
Key Takeaways
- Silent limit bypass is worse than no limit at all — in Starlette 1.2.1, setting
max_fieldsonrequest.form()gave a false sense of security forapplication/x-www-form-urlencodedrequests, the most common form encoding uv.lockis a security artifact — the vulnerability was detected directly in the lock file, which means scanning lock files (not justpyproject.toml) is essential for accurate dependency vulnerability detection- Upgrading from 1.2.1 to 1.3.1 is the only real fix — no amount of application-level workarounds in 1.2.1 would reliably enforce form field limits for URL-encoded content
- DoS vulnerabilities in form parsers are underrated — they don't leak data, so they're often deprioritized, but they can take down production services just as effectively as more "glamorous" vulnerabilities
- Test your security controls, not just your features — a test that submits 1,000 fields to a form endpoint with
max_fields=10would have caught this regression immediately
How Orbis AppSec Detected This
- Source: Inbound HTTP requests with
Content-Type: application/x-www-form-urlencoded, where the attacker controls the number and size of form fields - Sink: Starlette's internal
application/x-www-form-urlencodedform parser, invoked viarequest.form(), which consumed unbounded input without applying the configuredmax_fields/max_fileslimits - Missing control: The
max_fieldsandmax_filesenforcement logic was absent from the URL-encoded form parsing code path in Starlette 1.2.1, meaning no upper bound was applied to resource consumption - CWE: CWE-400 — Uncontrolled Resource Consumption
- Fix: The
starlettepackage inuv.lockwas upgraded from version 1.2.1 to 1.3.1, which applies form limit enforcement consistently across all supported content types
Orbis AppSec automatically detected this vulnerability and opened a pull request with the fix. Try Orbis AppSec on your repositories to find and fix issues like this automatically.
Conclusion
CVE-2026-54283 is a reminder that security isn't just about setting the right parameters — it's about verifying that those parameters actually do what you think they do. A developer who carefully set max_fields=100 on every form endpoint in their Starlette application was doing the right thing, but was unknowingly unprotected for URL-encoded form submissions in version 1.2.1.
The fix is simple: upgrade to Starlette 1.3.1. But the broader lesson is about trust and verification. Scan your lock files. Test your security controls under adversarial conditions. And don't assume that because a parameter exists, it's being enforced.
In web frameworks that handle arbitrary user input, the gap between "configured" and "enforced" can be the difference between a hardened service and one that falls over under a trivial HTTP flood.