Skip to content
Security Article

Cloudflare's Killer Regex Is Still Lurking in Your Stack

Seven years after the 27-minute global outage, most mainstream languages still default to exponential-time regex engines.

Emeka Okafor
Emeka Okafor
Security Editor · Aug 13, 2026 · 5 min read
Cloudflare's Killer Regex Is Still Lurking in Your Stack

On July 2, 2019, every Cloudflare edge server on the planet pinned its CPUs at nearly 100%, and for 27 minutes a measurable slice of the internet returned 502s. The cause wasn't a DDoS or a fiber cut. It was one new WAF rule containing, buried in a longer pattern, the fragment .*(?:.*=.*). Seven years later the incident is still worth studying — not as a war story, but because the underlying flaw is still the default behavior of the regex engine in Python, JavaScript, Java, and Ruby, and most teams still ship regexes against untrusted input without a second thought.

Review can't catch this class of bug

The rule that broke Cloudflare was a routine tweak meant to improve detection of inline JavaScript in attack payloads. It compiled, matched correctly on normal input, and passed CI. The problem only exists on adversarial or degenerate input: .*.*=.* asks a backtracking engine to try every possible way of splitting the input across three greedy wildcards before it can declare failure. Cloudflare's postmortem quantified it — matching x=x took the engine 23 steps, but append twenty more x characters and it took 555. The growth is superlinear, and on real request bodies it was enough to eat entire cores.

That's CWE-1333, catastrophic backtracking, and the crucial point is that it's a property of the engine, not the pattern author. Cloudflare's WAF ran Lua calling PCRE, which backtracks and has no built-in defense against runaway expressions. A CPU guardrail that would have contained the damage had been accidentally removed in an earlier refactor. And WAF rules deliberately bypassed Cloudflare's staged DOG/PIG/canary rollout — the whole point of managed rules is shipping a mitigation worldwide in seconds, so Quicksilver pushed the pattern to every edge server at 13:42 UTC. First alerts fired three minutes later; the global WAF kill switch went at 14:07.

Every layer of that failure was individually reasonable. Fast rule deployment is a feature. PCRE was the industry-standard engine. The regex was semantically correct. That's what makes this the canonical ReDoS incident: nobody was negligent, and it happened anyway. Stack Overflow had learned the same lesson three years earlier, when a whitespace-trimming regex hit a post containing ~20,000 consecutive spaces and took the site down for 34 minutes. Same mechanism, same "the pattern was fine" postmortem.

We've known the fix since before Unix

The maddening part is that linear-time regex matching isn't new research. Ken Thompson published the NFA construction in 1968, and Russ Cox's 2007 essay Regular Expression Matching Can Be Simple and Fast showed a Thompson NFA matching in microseconds where Perl's backtracker needed years. Cox's work became Google's RE2, which guarantees time linear in input length, always, for any pattern.

Backtracking won anyway, because it supports backreferences and arbitrary lookaround, and Perl-compatible became the compatibility target every language chased. So we ended up with an ecosystem where the theoretically sound engines — RE2, Go's regexp (an RE2 descendant), Rust's regex crate — are the alternatives, and the exponential-worst-case engines are the defaults. How often do you actually use a backreference? That's the trade you're making, usually without knowing it.

Cloudflare's own remediation shows where the industry is heading. The immediate fixes were restoring the CPU limit, auditing all 3,868 WAF rules, and committing to "either the re2 or Rust regex engine, which both have run-time guarantees." Longer term, they replaced the LuaJIT WAF entirely with a Rust-based engine and the wirefilter rule syntax, rolled out through 2021. Microsoft went the same direction: .NET 7 shipped RegexOptions.NonBacktracking, a full linear-time engine behind a one-flag opt-in. When the two companies that process the most hostile text on Earth both abandon backtracking for untrusted input, that's your signal.

The playbook for everyone else

If a regex runs against input you don't control — request bodies, user posts, log lines from other systems — treat the engine choice as a security decision, in this order:

Use a linear-time engine where one exists. In Go you already have it. In Rust, the default regex crate is safe by construction — it rejects backreferences at compile time rather than let you write a bomb. In Node, the re2 package wraps Google's engine with a mostly drop-in API. In .NET:

// Worst case drops from exponential to linear. No code changes beyond the flag.
var r = new Regex(pattern, RegexOptions.NonBacktracking);

If you're stuck with a backtracker, bound it. .NET has had a matchTimeout constructor argument since 4.5. PHP has pcre.backtrack_limit. Java and Python's re offer nothing — there's no timeout parameter, and a match on the main thread is uninterruptible, which is a strong argument for Python's third-party regex module (which supports timeout=) or for pushing validation behind a worker with a hard kill.

Lint patterns in CI. Static ReDoS checkers — recheck, Doyensec's regexploit — catch the classic shapes: nested quantifiers like (a+)+, and adjacent overlapping wildcards like the .*.*=.* that got Cloudflare. This is cheap and should be as standard as dependency audit.

Treat rule pushes as deploys. The regex was only half of Cloudflare's failure; the other half was a config channel that skipped every rollout safeguard code gets. If your system ships regexes, WAF rules, or routing configs globally, they need canaries and a global kill switch, full stop.

The uncomfortable conclusion

The 2019 outage is usually filed under "test your regexes," which is exactly the wrong lesson — Cloudflare's tests passed, and no realistic test suite explores exponential blowup you didn't know to look for. The right lesson is that backtracking regex engines are unsafe defaults, the same way string-concatenated SQL was an unsafe default, and the fix is structural: pick an engine with runtime guarantees, or wrap the unsafe one in hard limits. Infrastructure companies have already made that move. Application developers mostly haven't, because the defaults haven't changed — Python, JavaScript, and Java in 2026 still hand you an exponential-time matcher with no timeout and no warning. Until they do, every regex touching untrusted input is a small, silent bet that nobody sends you the wrong string.

Sources & further reading

  1. Details of the Cloudflare outage on July 2, 2019 — blog.cloudflare.com
  2. The Regex That Took Down Cloudflare for 27 Minutes — dev.to
  3. Web Application Firewall Causes Outage — infoq.com
  4. Cloudflare Announces New Web Application Firewall — infoq.com
  5. Regular Expression Matching Can Be Simple and Fast — swtch.com
Emeka Okafor
Written by
Emeka Okafor · Security Editor

Emeka has spent over a decade tracking threat actors, vulnerability disclosures, and the evolving landscape of application security, bringing a sharp continent-spanning perspective to his reporting. He's known for translating dense CVE advisories into clear, actionable context that developers and security teams alike actually read.

Discussion 1

Join the discussion

Sign in or create an account to comment and vote.

Nina Petrova @night_owl_nina · 4 hours ago

spent three hours last week debugging why a user-submitted validation regex was tanking our api responses before i realized we were running it against untrusted input with ruby's default engine. didn't even occur to me to check until i found this exact cloudflare post in my search results. now i'm paranoid about every regex that touches external data.

Related Reading