Back to Blog
high SEVERITY7 min read

How form limit bypass DoS happens in Python Starlette and how to fix it

CVE-2026-54283 is a high-severity denial-of-service vulnerability in Starlette where size limits set on `request.form()` were silently ignored for `application/x-www-form-urlencoded` content, allowing attackers to submit unbounded form data and exhaust server resources. The fix upgrades Starlette from version 1.2.1 to 1.3.1, which correctly enforces form size limits for all content types. Any Python web application using Starlette (including FastAPI-based services) that accepts form submissions

O
By Orbis AppSec
Published June 22, 2026Reviewed June 22, 2026

Answer Summary

CVE-2026-54283 is a denial-of-service vulnerability (CWE-400: Uncontrolled Resource Consumption) in Starlette's form parsing logic, where `max_fields` and `max_files` limits passed to `request.form()` were silently ignored when the request used `application/x-www-form-urlencoded` encoding. This allowed attackers to submit arbitrarily large or complex form payloads, exhausting server memory and CPU. The fix is to upgrade Starlette from version 1.2.1 to 1.3.1, which correctly enforces all configured limits for both `multipart/form-data` and `application/x-www-form-urlencoded` content types.

Vulnerability at a Glance

cweCWE-400
fixUpgrade Starlette from 1.2.1 to 1.3.1, which enforces form limits for all content types
riskAttackers can exhaust server memory/CPU by submitting unbounded URL-encoded form data
languagePython
root causeStarlette's form parser silently ignored `max_fields`/`max_files` limits for `application/x-www-form-urlencoded` requests
vulnerabilityForm size limit bypass enabling Denial of Service

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 uploads
  • application/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_fields on request.form() gave a false sense of security for application/x-www-form-urlencoded requests, the most common form encoding
  • uv.lock is a security artifact — the vulnerability was detected directly in the lock file, which means scanning lock files (not just pyproject.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=10 would 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-urlencoded form parser, invoked via request.form(), which consumed unbounded input without applying the configured max_fields/max_files limits
  • Missing control: The max_fields and max_files enforcement 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 starlette package in uv.lock was 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.


References

Frequently Asked Questions

What is the Starlette form limit bypass vulnerability (CVE-2026-54283)?

It's a flaw where size and field-count limits passed to Starlette's `request.form()` were silently ignored for `application/x-www-form-urlencoded` requests, allowing attackers to send unbounded form data and cause a denial of service.

How do you prevent form-based DoS in Python Starlette?

Upgrade to Starlette 1.3.1 or later, which correctly enforces `max_fields`, `max_files`, and size limits for all form content types. Also apply server-level request body size limits as a defense-in-depth measure.

What CWE is this form limit bypass vulnerability?

CWE-400: Uncontrolled Resource Consumption. The server consumes unbounded memory and CPU processing arbitrarily large form submissions because the intended limits are never applied.

Is setting max_fields in request.form() enough to prevent DoS in Starlette 1.2.1?

No. In Starlette 1.2.1, those limits are silently ignored for `application/x-www-form-urlencoded` content. The parameter appears to work but has no effect, which is why upgrading to 1.3.1 is required.

Can static analysis detect this form limit bypass vulnerability?

Yes — tools like Trivy can detect known vulnerable dependency versions in lock files like `uv.lock`, which is exactly how CVE-2026-54283 was identified in this codebase.

View the Security Fix

Check out the pull request that fixed this vulnerability

View PR #3673

Related Articles

critical

How command injection happens in Python subprocess and how to fix it

A critical command injection vulnerability was discovered in `script/llm_semantic_analyzer.py` at line 394, where user-controlled input (API keys and model parameters) was interpolated directly into shell commands passed to `subprocess.run` with `shell=True`. An attacker who could control these parameters could inject shell metacharacters like `; rm -rf /` or `$(whoami)` to execute arbitrary commands. The fix sanitizes all user input before it reaches shell execution.

critical

How path traversal happens in Python os.path and how to fix it

A critical path traversal vulnerability in the TRL backend allowed attackers to read arbitrary system files like `/etc/passwd` and `/proc/self/environ` through the gRPC fine-tuning API. The `_do_training` method passed user-controlled `dataset_source` directly to `os.path.exists()` and `load_dataset()` without validation. The fix implements strict directory containment checks using `os.path.realpath()` to ensure all file operations stay within allowed directories.

critical

How command injection happens in Python subprocess and how to fix it

A command injection vulnerability in `skills/skill-comply/scripts/runner.py` allowed attackers who could influence skill definition files to execute arbitrary binaries on the host system via `subprocess.run()`. The fix introduces an explicit allowlist of permitted executables (`ALLOWED_SETUP_EXECUTABLES`) that gates every command before it reaches the subprocess call at line 110. This closes a significant attack surface in the skill-comply pipeline without breaking legitimate setup workflows.

critical

How command injection happens in Python subprocess and how to fix it

A critical command injection vulnerability was discovered in a CGI script that processed HTTP requests using `subprocess.check_output()` with `shell=True`. Attackers could inject arbitrary shell commands through URL parameters using metacharacters like semicolons, pipes, or backticks. The fix converts the command from a string to a list and sets `shell=False`, preventing shell interpretation of user input.

critical

How reflected XSS happens in Jinja2 template rendering and how to fix it

A reflected cross-site scripting (XSS) vulnerability was discovered in the similarity search HTML template where user input from the `query` form parameter was rendered directly into an HTML attribute without proper escaping. An attacker could inject malicious JavaScript by crafting a search query containing attribute-breaking payloads like `" onfocus="alert(document.cookie)" autofocus="`, which would execute in the victim's browser.

high

How buffer overflow via insecure strcpy/strncpy happens in C textbox widgets and how to fix it

A high-severity buffer overflow vulnerability was discovered in the Aroma UI framework's textbox widget where `strncpy()` was used to copy user-provided text without guaranteed null-termination safety. The fix replaces the dangerous `strncpy()` pattern with `snprintf()`, which automatically handles buffer boundaries and null-termination in a single, safer operation.