How Silent Form Limit Bypasses Happen in Starlette and How to Fix Them
Vulnerability at a Glance
| Field | Detail |
|---|---|
| Vulnerability | Denial of Service via silently ignored form size limits |
| CWE | CWE-770 — Allocation of Resources Without Limits or Throttling |
| Language | Python |
| Framework | Starlette |
| Risk | Unbounded memory consumption from URL-encoded form payloads |
| Root Cause | Form size limits not enforced for application/x-www-form-urlencoded |
| Fix | Upgrade Starlette from 0.49.1 → 0.50.0 |
Introduction
The agent/sandbox/uv.lock file in this repository pins Starlette at version 0.49.1 — a version that contains a subtle but dangerous flaw in how it processes HTML form submissions. When application code calls request.form() with size-limiting parameters, developers reasonably expect those limits to be enforced. In Starlette 0.49.1, however, those limits are silently ignored for the application/x-www-form-urlencoded content type, the default encoding used by virtually every HTML <form> element on the web.
This is the kind of vulnerability that's particularly insidious: the code looks safe, the developer believes it's protected, but the protection simply isn't there. An attacker who knows this can bypass what appears to be a hardened endpoint and submit payloads large enough to exhaust server memory or CPU — a classic Denial of Service attack requiring nothing more than an HTTP client.
The Vulnerability Explained
What request.form() Is Supposed to Do
Starlette's request.form() is an async method that parses incoming form data from HTTP request bodies. It supports two content types:
multipart/form-data— used for file uploadsapplication/x-www-form-urlencoded— the default for standard HTML forms
Developers can pass limit parameters to cap how much data is processed:
# Developer's intent: limit form fields to prevent resource exhaustion
form_data = await request.form(
max_fields=100,
max_files=10,
max_part_size=1_000_000 # 1 MB per part
)
This looks correct. The problem is that in Starlette 0.49.1, these limits were only wired into the multipart parser. When the incoming Content-Type header was application/x-www-form-urlencoded, the parser read the entire body without checking any of those configured limits.
The Silent Failure
The vulnerability is not a crash or an exception — it's silence. The limits are accepted as valid arguments, no warning is raised, no error is thrown, and the developer gets no feedback that their carefully configured guardrails are doing nothing. This is what makes CVE-2026-54283 especially dangerous: it creates a false sense of security.
A vulnerable endpoint might look like this:
from starlette.applications import Starlette
from starlette.routing import Route
from starlette.responses import JSONResponse
async def submit_form(request):
# In Starlette 0.49.1, max_fields is ignored for
# application/x-www-form-urlencoded — limits are silently dropped
form = await request.form(max_fields=50, max_part_size=64_000)
return JSONResponse({"status": "ok"})
app = Starlette(routes=[Route("/submit", submit_form, methods=["POST"])])
How an Attacker Exploits This
An attacker doesn't need authentication, special knowledge of the application, or sophisticated tooling. They only need to know the endpoint accepts form submissions. A simple attack looks like this:
# Generate a massive URL-encoded form payload and POST it
python3 -c "
import urllib.parse, sys
# Create a payload with thousands of fields and large values
payload = '&'.join(f'field_{i}=' + 'A' * 10000 for i in range(10000))
print(payload)
" | curl -s -X POST \
-H "Content-Type: application/x-www-form-urlencoded" \
--data-binary @- \
https://target-app.example.com/submit
Because Starlette 0.49.1 ignores max_fields and max_part_size for URL-encoded bodies, this payload — roughly 100 MB of form data — is read entirely into memory and parsed. Repeat this across multiple concurrent connections and the server's memory is exhausted, taking down the application for legitimate users.
Real-World Impact for This Repository
The vulnerable dependency lives in agent/sandbox/uv.lock, which governs the Python environment for the agent's sandbox component. If this component exposes any HTTP endpoints that process form submissions (a common pattern in agent frameworks that accept configuration, commands, or data via web interfaces), those endpoints are exposed to this resource exhaustion attack without any of the configured limits providing protection.
The Fix
What Changed
The fix is a targeted dependency upgrade in agent/sandbox/uv.lock, bumping Starlette from 0.49.1 to 0.50.0:
[[package]]
name = "starlette"
-version = "0.49.1"
+version = "0.50.0"
source = { registry = "https://pypi.tuna.tsinghua.edu.cn/simple" }
dependencies = [
{ name = "anyio" },
]
-sdist = { url = "...starlette-0.49.1.tar.gz", hash = "sha256:481a43b71e24ed8c43b11ea02f5353d77840e01480881b8cb5a26b8cae64a8cb" }
+sdist = { url = "...starlette-0.50.0.tar.gz", hash = "sha256:a2a17b22203254bcbc2e1f926d2d55f3f9497f769416b3190768befe598fa3ca" }
wheels = [
- { url = "...starlette-0.49.1-py3-none-any.whl", hash = "sha256:d92ce9f07e4a3caa3ac13a79523bd18e3bc0042bb8ff2d759a8e7dd0e1859875" }
+ { url = "...starlette-0.50.0-py3-none-any.whl", hash = "sha256:9e5391843ec9b6e472eed1365a78c8098cfceb7a74bfd4d6b1c0c0095efb3bca" }
]
What Starlette 0.50.0 Actually Fixes
In version 0.50.0, Starlette's form parsing logic was updated so that limits passed to request.form() are enforced for both multipart/form-data and application/x-www-form-urlencoded content types. The URL-encoded form parser now respects max_fields and max_part_size, raising appropriate errors when payloads exceed configured thresholds rather than silently consuming them.
After the upgrade, the same developer code that was previously unprotected:
form = await request.form(max_fields=50, max_part_size=64_000)
...now actually enforces those limits regardless of whether the client sends multipart/form-data or application/x-www-form-urlencoded. An oversized URL-encoded payload will be rejected before it's fully read into memory.
Why Only the Lock File Changed
The change is confined to agent/sandbox/uv.lock because uv.lock is the authoritative record of exactly which package versions are installed in the sandbox environment. Updating this file is the correct and complete way to deploy the patched version — no application code changes are required because the fix lives entirely within the Starlette library itself.
Prevention & Best Practices
1. Don't Rely Solely on Application-Level Limits
Even with the fix in place, defense in depth is valuable. Configure your reverse proxy (nginx, Caddy, AWS ALB) to enforce a maximum request body size before traffic reaches your Python application:
# nginx: reject bodies larger than 10MB at the proxy layer
client_max_body_size 10M;
This catches oversized requests before Starlette ever sees them, regardless of which version you're running.
2. Pin Dependencies and Audit Them Regularly
The vulnerability was detectable precisely because uv.lock pins exact versions. Use lock files for all Python projects and integrate a scanner like Trivy, pip-audit, or Safety into your CI pipeline:
# Scan your lock file for known CVEs
trivy fs --scanners vuln agent/sandbox/uv.lock
# Or use pip-audit
pip-audit -r requirements.txt
3. Test Your Limits, Don't Just Set Them
When you configure resource limits in your framework, write tests that verify those limits are actually enforced:
import pytest
from starlette.testclient import TestClient
def test_form_size_limit_enforced():
"""Verify that max_fields is actually enforced for URL-encoded forms."""
client = TestClient(app)
# Generate a payload exceeding the configured limit
oversized = "&".join(f"field_{i}=value" for i in range(200))
response = client.post(
"/submit",
content=oversized,
headers={"Content-Type": "application/x-www-form-urlencoded"},
)
# Should be rejected, not processed
assert response.status_code in (400, 413, 422)
This kind of test would have caught the silent bypass in 0.49.1 immediately.
4. Follow OWASP Guidance on Resource Limits
The OWASP API Security Top 10 lists "Unrestricted Resource Consumption" (API4:2023) as a top concern. Key recommendations include:
- Enforce maximum payload sizes at multiple layers (proxy, framework, application)
- Set timeouts on all I/O operations, including form parsing
- Monitor for anomalous request sizes in your logging/observability stack
5. Understand CWE-770
This vulnerability maps to CWE-770: Allocation of Resources Without Limits or Throttling. Whenever you're writing code that reads user-supplied data into memory — forms, file uploads, JSON bodies, streaming data — always ask: what is the worst-case allocation if a malicious actor controls this input?
Key Takeaways
- Silent failures are the most dangerous kind: Starlette 0.49.1 accepted
max_fieldsandmax_part_sizeparameters without error but ignored them for URL-encoded forms — giving developers false confidence their endpoints were protected. application/x-www-form-urlencodedis the default form encoding: This is not an exotic edge case. Standard HTML<form>elements use this content type by default, meaning the vulnerable code path was the most common one.- Lock files enable precise vulnerability detection: Because
agent/sandbox/uv.lockpins the exact version0.49.1, Trivy could definitively identify the vulnerable package and flag it for remediation. - The fix requires no application code changes: Upgrading to Starlette 0.50.0 is sufficient — the limit enforcement logic lives in the library, so existing calls to
request.form()with limit parameters automatically gain correct behavior. - Always validate that resource limits work with a test: Setting a limit is not the same as enforcing a limit. A simple integration test that sends an oversized payload would have caught this bypass immediately.
How Orbis AppSec Detected This
- Source: Incoming HTTP request body with
Content-Type: application/x-www-form-urlencoded, containing an attacker-controlled payload of arbitrary size - Sink:
request.form()in Starlette's URL-encoded form parser (starlette/formparsers.py), which allocated memory proportional to the full request body size without enforcing themax_fieldsormax_part_sizelimits passed by the caller - Missing control: Enforcement of developer-configured size limits for the
application/x-www-form-urlencodedcode path — limits were checked only in the multipart parser branch - CWE: CWE-770 — Allocation of Resources Without Limits or Throttling
- Fix: Upgraded Starlette from
0.49.1to0.50.0inagent/sandbox/uv.lock, where the URL-encoded form parser correctly enforces all configured limits
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 controls are only valuable if they're actually enforced. In Starlette 0.49.1, the request.form() API accepted resource limit parameters in good faith but silently discarded them for URL-encoded form bodies — the most common form encoding on the web. Any endpoint that relied on those limits for protection against resource exhaustion was, in practice, unprotected.
The fix is straightforward: upgrade to Starlette 0.50.0, where limits are enforced uniformly across all form content types. But the broader lesson is architectural: resource limits should be tested, not just configured; they should be layered (proxy + framework + application); and dependency scanners should be part of every CI pipeline so that vulnerabilities like this are caught before they reach production.