A single crafted HTTP POST request was, for years, enough to pin a Flask worker's CPU and take a production service offline. That is the practical impact of CVE-2019-1010083, a denial-of-service flaw in Werkzeug, the WSGI toolkit that underpins Flask and a handful of other Python web frameworks. The vulnerability lives in Werkzeug's multipart/form-data parser, and because that parser runs on every request an application handles as a file or form upload, the exposure was broad: any Flask (or bare Werkzeug) application accepting multipart uploads before the fix landed was a candidate target. No authentication, no special privileges, and no user interaction were required — just a POST request with a specially structured body.
What CVE-2019-1010083 actually is
Werkzeug parses incoming multipart/form-data bodies by scanning for boundary markers and splitting the payload into individual form fields and file parts. Prior to the fix, that scanning logic did not bound the cost of processing pathological input. An attacker could submit a multipart body engineered with an excessive number of parts, or with boundary-like byte sequences repeated throughout the payload, and drive the parser into markedly worse-than-linear processing time relative to input size. In practice this meant a modestly sized request — well within typical body-size limits — could consume disproportionate CPU and memory on the server while Werkzeug worked through the payload.
The effect is a classic algorithmic-complexity denial-of-service: the attacker doesn't need to send gigabytes of data or thousands of concurrent connections. A handful of crafted requests against a synchronous WSGI worker (the default deployment shape for Flask behind Gunicorn's sync worker class, uWSGI, or the Werkzeug development server) is enough to occupy that worker for an extended period, degrading or fully blocking the application's ability to serve legitimate traffic. Multi-threaded and multi-process deployments are more resilient in aggregate but remain vulnerable request-by-request, and a sustained stream of crafted requests can still exhaust a worker pool.
This class of bug is easy to underestimate because it doesn't look like a "real" exploit — there's no shell, no data exfiltration, no memory corruption. But for any team measuring uptime and SLAs, an unauthenticated, low-effort way to peg CPU on a production web tier is a serious operational risk, and it's precisely the kind of finding that gets deprioritized in a backlog until it causes an incident.
Affected versions and components
- Component: Werkzeug's multipart form-data parsing logic (
werkzeug.formparser), used internally whenever a request is parsed with aContent-Typeofmultipart/form-data. - Affected versions: Werkzeug releases prior to 0.15.3.
- Fixed in: Werkzeug 0.15.3, released in May 2019.
- Downstream impact: Flask depends directly on Werkzeug for request parsing, so any Flask application pinned to a pre-0.15.3 Werkzeug release inherited the flaw regardless of the Flask version itself. Other frameworks and tools built on Werkzeug (custom WSGI middleware, Quart's early compatibility layers, and internal tooling that imports
werkzeug.formparserdirectly) are affected under the same condition. Applications that never accept multipart uploads, or that reject them upstream before they reach Werkzeug, are not exposed.
Because Werkzeug is a transitive dependency for most Flask apps rather than something developers interact with directly, this is a textbook example of a vulnerability that hides in the dependency tree — teams scanning only their direct requirements.txt entries for "Flask" without resolving the Werkzeug version actually installed can easily miss it.
CVSS, EPSS, and KEV context
CVE-2019-1010083 carries a CVSS v3 base score of 7.5 (High), reflecting a network-exploitable, low-complexity attack with no privileges or user interaction required, and a high impact to availability with no direct impact to confidentiality or integrity (AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H). A 7.5 score is worth taking seriously in a vulnerability-management pipeline even though it's "only" a DoS — availability-impacting bugs with this attack profile are cheap to weaponize and don't require an attacker to have any foothold.
On the likelihood side, EPSS (the Exploit Prediction Scoring System) places this CVE in the low percentile range as of this writing, consistent with an old, low-profile bug that has not been observed driving mass exploitation. It is not present in CISA's Known Exploited Vulnerabilities (KEV) catalog, meaning there's no confirmed evidence of active, widespread exploitation being tracked by federal agencies. That combination — high CVSS, low EPSS, absent from KEV — is a common and important pattern: it tells you the ceiling of impact is high if someone bothers to target you, but the observed exploitation pressure in the wild has been low. For internet-facing applications that still run pre-2019 Werkzeug releases (more common than teams expect in legacy internal tools, vendored appliances, and containers built from stale base images), that risk profile still warrants remediation rather than acceptance.
It's also worth noting that CVE-2019-1010083 belongs to a batch of CVE IDs assigned in 2019 through a third-party bulk-submission process covering multiple popular open-source Python projects, rather than being requested by the Pallets maintainers themselves. That process drew some criticism at the time for its lack of coordination with upstream maintainers, but the underlying performance issue in the multipart parser was genuine, and Pallets shipped a corresponding fix in the 0.15.3 release regardless of how the CVE itself was filed. Security teams should treat the fix and the advisory as legitimate even where the CVE-assignment history is unusual.
Timeline
- Pre-2019: The unbounded-cost multipart parsing behavior exists in Werkzeug releases going back through the 0.x series.
- May 2019: Werkzeug 0.15.3 is released, correcting the multipart parser's handling of pathological input and eliminating the disproportionate CPU/memory consumption.
- 2019 (bulk CVE assignment): CVE-2019-1010083 is assigned as part of a broader third-party effort to file CVEs against unpatched-looking issues in popular Python packages, formally cataloging the flaw against pre-0.15.3 Werkzeug.
- Ongoing: The issue remains fixed in all subsequent Werkzeug releases, including the actively maintained 2.x and 3.x branches, which carry substantial additional hardening around request parsing, size limits, and streaming behavior well beyond the original fix.
Remediation steps
- Upgrade Werkzeug to 0.15.3 or later. In practice, teams should target a current, actively maintained release (the 2.x or 3.x branch) rather than the minimum patched version, since later releases include additional parsing hardening and no longer receive security backports on the 0.x line.
- Upgrade Flask in lockstep. Confirm the Flask version in use resolves a compatible, patched Werkzeug via its dependency constraints — don't just bump Flask and assume Werkzeug follows; pin or verify the resolved Werkzeug version explicitly in your lockfile.
- Audit transitive dependents. Search your dependency graph for any package that imports
werkzeug.formparserorwerkzeug.wsgidirectly (custom WSGI middleware, internal tooling, vendored frameworks) and confirm those paths are also on a patched Werkzeug. - Add defense-in-depth at the edge. Where immediate upgrades aren't possible (legacy systems, vendor appliances), enforce request body size limits and multipart part-count limits at a reverse proxy or WAF in front of the application, and set aggressive worker timeouts (e.g., Gunicorn
--timeout, uWSGIharakiri) so a stuck request-parsing operation gets killed rather than starving the worker pool indefinitely. - Rebuild and redeploy container images. If your services are built from base images or vendored dependencies frozen at an old point in time, confirm a rebuild actually pulls the patched Werkzeug rather than reusing a cached layer with the vulnerable version.
- Verify with a scan, not an assumption. After patching, re-run dependency/SBOM scanning against the deployed artifact (not just the source repo) to confirm the resolved, installed Werkzeug version is actually the patched one in every environment — staging and production included.
How Safeguard Helps
Legacy DoS-class CVEs like this one are exactly where alert fatigue does the most damage: a CVSS 7.5 finding in a five-year-old dependency looks urgent on paper but often gets buried under hundreds of other open findings, most of which aren't actually exploitable in your environment. Safeguard's reachability analysis resolves that ambiguity for CVE-2019-1010083 specifically by determining whether your application code actually invokes multipart form parsing — flagging services that expose file-upload or form-submission endpoints as truly at risk, while deprioritizing services that never call into the vulnerable parser at all. Griffin AI layers business and deployment context on top of that reachability signal to help teams triage this alongside everything else in the backlog, instead of treating every CVSS score as equally urgent. Continuous SBOM generation and ingestion give you visibility into every service still running a pre-0.15.3 Werkzeug — including versions buried three or four layers deep in a Flask or Quart dependency tree, or frozen inside a stale container base image, that manual dependency review would likely miss. And where remediation is confirmed necessary, Safeguard can open an auto-fix pull request that bumps Werkzeug (and Flask, where required) to a patched, currently maintained release, so the fix ships without a manual dependency-chase across every affected repository.