Werkzeug EOL and How to Keep Your Python Apps Secure

php2shell patches (CVE-2026-109618 and CVE-2026-109619) for EOL PHP are out now. With sales replies slower than usual due to high demand, the fastest route to patched packages is to buy ELS for Runtimes online (opens in new tab). Read the patch FAQ

Go straight to an AI summary of this blog post instead

  • Summarize this post with Perplexity
  • Summarize this post with Gemini

Key Takeaways

  • Unsupported Werkzeug means upstream fixes stop, leaving affected applications without an upstream patch when new vulnerabilities emerge.
  • Rushed upgrades can introduce production bugs, dependency issues, technical debt, and unplanned costs.
  • TuxCare ELS keeps older Werkzeug versions patched while you plan the right time to upgrade.

You built something solid on Werkzeug. The routing works. The request handling is clean. The WSGI layer does exactly what it is supposed to do. Then your dependency scanner lights up, or someone from the security team asks which Werkzeug version is running in the container, and the answer turns out to be 2.2.3, a release that dates back to 2023.

Now what?

This is a more common spot than it looks, largely because almost nobody chooses their Werkzeug version on purpose. Flask pulls it in, pip resolves it, and it sits there quietly until something forces the question. So let us walk through what actually happens when a Werkzeug branch stops getting updates, and what you can realistically do about it.

How Werkzeug Releases Actually Work

Werkzeug is maintained by the Pallets project alongside Flask, Jinja, and Click. Pallets does not publish a long-term support calendar with fixed maintenance windows for each branch, the way Python or Node.js does. There is no announced end-of-life date for Werkzeug 2.2 sitting on a roadmap somewhere. Support has to be inferred from what the maintainers actually ship.

The broad shape is easy enough to follow. Werkzeug 2.x dropped Python 2 and 3.5 and moved the codebase toward type annotations. Werkzeug 3.x kept going, removing deprecated APIs and raising the Python floor, and now requires Python 3.9 or newer. The active line today is 3.1.x, with 3.1.8, released on April 2, 2026, the latest stable version as of August 2026.

Here is the part that trips people up. The intuitive model, that a new major release immediately kills the previous branch, is not reliable. Werkzeug 3.0.0 shipped at the end of September 2023, and 2.3.8 still arrived on November 8, 2023 carrying a security fix. Backports do happen. They are just unpredictable and unpromised.

What the release history does show is where the attention goes. The 3.0 line got its last release, 3.0.6, in late October 2024, days before 3.1.0 shipped, and nothing since. The 2.x line ended at 2.3.8. So in practice 3.1.x is the only branch still receiving releases, while the earlier branches look inactive without ever having been formally declared dead. That is arguably worse than a published EOL date, because you cannot point an auditor at a calendar and you cannot plan against a maintenance window that was never offered.

If you are not sure where you stand, pip show werkzeug answers it in about two seconds. If the version does not start with 3.1, do not count on upstream shipping a fix for whatever gets disclosed next.

What an Unsupported Werkzeug Actually Means in Production

Support ends. The application keeps running. Nothing visibly breaks. So why does it matter?

Because patches stopping does not make vulnerabilities stop. The risk is not that attackers keep a list of Werkzeug laggards. It is simpler and more boring than that. A flaw gets disclosed, it affects your branch, and there is no upstream remediation path. Werkzeug sits underneath Flask, so that gap gets inherited by whatever you built on top of it.

Two disclosures illustrate the problem well.

CVE-2024-34069 is a flaw in Werkzeug’s interactive debugger, rated 7.5 High by both NVD and the Pallets advisory. Exploitation is narrow. The attacker has to get a developer to interact with a domain and subdomain they control, get them to enter the debugger PIN, and guess a URL in the application that triggers the debugger. If all three conditions line up, the result is code execution on the developer’s machine, even with the debugger bound to localhost. It was fixed in 3.0.3.

That fix is the whole point. Werkzeug 3.0.3 is the first release that contains it, and every later 3.x release carries it forward. What does not exist is a 2.x release with the patch, including 2.3.8, the final 2.x release. If your stack is on the 2.x line, there is no version on that branch you can move to in order to resolve this one.

Learn more about Werkzeug Security and current vulnerabilities.

CVE-2023-46136 is a denial of service in the multipart form parser. If an upload starts with a CR or LF and is then followed by megabytes without those characters, the parser appends bytes chunk by chunk into an internal bytearray and rescans the growing buffer for the boundary, burning CPU and blocking the worker from serving anything else. It affects 2.3.7 and earlier along with 3.0.0, and was fixed in 2.3.8 and 3.0.1. Worth knowing before you argue about it in a ticket: NVD scores it 7.5 High, while the Pallets advisory scores it 5.7 Moderate, because the two disagree on attack vector and required privileges. Different scanners may report different severities depending on which feed they pull from. Exposure applies to applications with an endpoint that actually parses attacker-supplied multipart data.

On a maintained branch, both of these are a version bump. On an unsupported one, a newly disclosed vulnerability stays unpatched upstream until you either upgrade or source a backport somewhere else.

Security and Compliance Risks After Support Ends

PCI DSS v4.0.1, the current version of the standard, requires under Requirement 6.3.3 that patches for critical vulnerabilities be installed within one month of release, with other applicable patches handled on the organization’s documented risk-based timeline. Version 4.0 had extended that one-month clock to high-severity items as well, and 4.0.1 returned to the narrower wording. The direction of that revision matters less than the assumption underneath it, which is that a patch exists to install.

When there is no upstream fix for your branch, you are not late on remediation. You are outside the model the requirement was written around, and that is a harder conversation to have with an assessor.

SOC 2 does not contain a rule against end-of-life software, so be careful how you frame this internally. What it does is invite questions about vulnerability management and whether you can demonstrate a working remediation path. An unsupported dependency with open findings and no available patch is the kind of thing that generates follow-up requests, slows down security questionnaires, and complicates customer risk reviews at renewal time.

Your Options When Support Runs Out

There are three, and the right one depends less on the CVE that flagged you than on how much Werkzeug surface area your application actually has.

Upgrade to the Latest Werkzeug

The standard answer, and for plenty of teams the correct one. Werkzeug 3.1.x is mature, the API is cleaner, and the type annotations are genuinely useful.

Where it gets interesting is the assumption that this is a one-line change. Werkzeug does not move on its own. Flask releases generally declare a minimum Werkzeug version rather than pinning an upper bound, which leaves pip free to resolve a newer Werkzeug than your Flask version was ever tested against. That is not hypothetical. Werkzeug 3.0 removed url_quote, which Flask 2.2.2 imports at the top of app.py, so the pairing fails on import. Werkzeug 3.1 then removed werkzeug.__version__, which the Flask 2.2.4 test client still reads when it builds a user agent string, producing an AttributeError the first time anyone runs the suite in a fresh container. That one is written up in Flask issue 5652.

So upgrading Werkzeug may well mean upgrading Flask, which means auditing every extension pinned against Flask, which means a compatibility pass across the dependency tree. If your codebase is small, your extension list is short, and your test coverage is honest, do it now and stop reading. If it is a mature application with a decade of accumulated middleware, budget properly.

Migrate to a Different Framework

Some teams look at an unsupported dependency and decide the real answer is somewhere else entirely. Django if you want batteries included, FastAPI if you want an async API framework built on Starlette, Starlette itself if you want the ASGI toolkit without the opinions, Bottle or Falcon if you want something lighter. FastAPI in particular has real momentum, an async-first design, and OpenAPI docs generated for free.

None of them are drop-in replacements for Werkzeug. Route logic, request parsing, template rendering, the middleware stack, error handling, and deployment configuration all get reworked. For an application with meaningful Werkzeug surface area, a properly planned and tested migration becomes a multi-sprint or multi-quarter project, not a cleanup task. Do it because you already wanted to be async, not because a scanner flagged you on a Tuesday.

Use Extended Support and Buy Yourself Time

The third option is to keep the version you are on and keep receiving security patches for it. That takes the timeline out of the hands of whoever files the next disclosure and puts it back on your roadmap. There is a full breakdown of how this works further down.

The Real Cost of Rushing an Upgrade

Picture a thirty-person SaaS team told to clear a high-severity finding before a SOC 2 window closes in three weeks. The upgrade itself is not the hard part. The fallout is.

Bugs the test suite missed start surfacing in production, and support picks up a run of tickets about behavior that used to be fine. Transitive dependencies contribute their own edge cases, the kind that only appear under real load, which produces intermittent failures that are miserable to reproduce and worse to explain during an incident review.

Then the shortcuts start. Refactors get skipped, workarounds pile up, and everyone agrees to fix it properly later. Six months on, later has not arrived and the codebase is in worse shape than before the upgrade that was supposed to clean it up.

The costs follow the same pattern. Contractor hours nobody budgeted, roadmap commitments that slip a quarter, and senior engineers spending their week reading pip resolver output instead of building the thing customers asked for. None of that shows up in the original estimate, because the original estimate was for the version bump.

How TuxCare ELS for Werkzeug Works

TuxCare has spent 15 years patching end-of-life open source, with 420,000+ patches delivered and more than 14,000 vulnerabilities fixed across operating systems, runtimes, libraries, and applications. Werkzeug is covered through TuxCare’s ELS for Libraries.

Covered versions currently include Werkzeug 1.0.1, 2.2.3, and 2.3.8, with other versions available on request.

What Makes the Approach Different

Werkzeug’s dependency surface is unusually small. The 2.x releases declare exactly one required runtime dependency, MarkupSafe, and 1.0.1 declares none at all, which keeps the coverage question simple. ELS includes security fixes for direct and transitive dependencies at no extra cost, which matters more than it sounds, because end-of-life risk can sit a layer below the package you are actually tracking.

The patching process runs on automation with people checking the output. TuxCare tracks CVEs around the clock across the NVD, vendor advisories, and GitHub security advisories, backports upstream fixes using algorithmic and AI-assisted automation with expert validation, then puts every build through integrity, functionality, and regression testing before signing it and publishing it to the ELS repositories. If you want the mechanics rather than the summary, the Open Source Patching Process write-up covers it properly.

Integration stays inside pip. You either point pip at the TuxCare repository with index-url, or you keep public PyPI and add it as an extra-index-url, pinning the patched build directly. Patched releases carry a version string like werkzeug==1.0.1.post1+tuxcare, so the patched build stays identifiable wherever your resolved versions are recorded. It is a repository configuration change with credentials, not a rebuild of your deployment pipeline.

Delivery is SLA-backed rather than best-effort, and the packages are signed and served from TuxCare-hosted repositories, with 24/7 help for triage and patch requests when something needs a human. For audit work, each patched release comes with SBOM data and there is a VEX feed alongside it, so VEX-aware tooling can tell remediated findings apart from the ones that still need action.

On cost, the honest framing is not that extended support is cheap. It is that it moves the migration engineering, the compatibility testing, and the regression work out of an emergency window and into a planned one, where it costs what it should cost.

How to Get Started

If you want to know what coverage actually looks like for your version, the TuxCare CVE tracker lists the Werkzeug vulnerabilities that have been patched, and the ELS documentation covers supported versions and the install options in detail.

For supported versions, pricing, and how implementation works in practice, the Endless Lifecycle Support for Werkzeug page is the place to start.

Do Not Let an Unsupported Werkzeug Put Your Systems at Risk

Werkzeug’s position as the WSGI layer under Flask means lifecycle decisions about it are rarely contained. When an unsupported version carries a flaw in code your application actually exercises, your application carries it too, and you find out from a scanner or a questionnaire rather than from a release note.

None of that requires panic. You can plan a Werkzeug upgrade for a quarter where you have the room to do it well. You can move to a different framework if there is a real technical reason beyond the scanner. Or you can keep receiving patches on the version you already run while you decide, which helps you hold your security posture and gives you real remediation evidence for compliance reviews, without handing your roadmap to whoever files the next CVE.

The teams that come through this well are rarely the ones with the biggest budgets. They are the ones who worked out which version they were actually on, understood what was and was not still being maintained, and chose deliberately instead of reacting.

If any of this is sitting on your plate right now, Endless Lifecycle Support for Open-Source Software shows how extended support covers end-of-life components across the rest of your stack for as long as you need them.

Summary
Werkzeug EOL and How to Keep Your Python Apps Secure
Article Name
Werkzeug EOL and How to Keep Your Python Apps Secure
Description
When Werkzeug support ends, the CVEs keep coming. Here is what an unsupported version means for Flask apps and how TuxCare ELS keeps you patched.
Author
Publisher Name
TuxCare
Publisher Logo

Join 4,500+ Linux & Open Source Professionals!

2x a month. No spam.

Table of Contents
Get the open-source security answers you need

Join 4,500+ Linux & Open Source Professionals!

2x a month. No spam.