Debugging Catastrophic Backtracking

Find out whether a pattern is vulnerable to catastrophic backtracking before it reaches production, using a 500ms worker budget that catches a runaway pattern without freezing your browser.

Debugging Catastrophic Backtracking Before It Ships

A regular expression that works perfectly on every test string you tried can still take down a server the first time someone feeds it a hostile input. Nested or overlapping quantifiers, a pattern like (a+)+, force the matching engine to try an enormous number of ways to split the same text, and that failure mode has a name specific enough to have its own security advisories: catastrophic backtracking.1 Checking a suspicious pattern here, before it reaches production code, is far cheaper than discovering the vulnerability from an incident report.

Run this check yourself in the Regex Tester & Match Debugger.

Open in the tool →

Why some patterns explode instead of just failing to match

Ordinary pattern failures happen fast. The engine scans the input, finds no valid match, and reports failure in a time roughly proportional to the length of what it scanned. Catastrophic backtracking is different in kind, not just degree, because certain quantifier combinations create exponentially many ways to divide the same string among the pattern's repeating groups.

A pattern like (a+)+$ tested against a long run of the letter "a" followed by one character that breaks the match forces the engine to retry every possible split of that run before it can conclude failure. Security researchers call the attack that exploits this Regular Expression Denial of Service, or ReDoS, and it is a well-documented category with its own entry in the OWASP attack catalog.1 The pattern itself is not malicious; the interaction between its structure and a crafted input is what turns it into a liability.

Watching the timeout guard catch the blow-up

This tester treats that risk as a design requirement rather than an afterthought. The risk is not hypothetical: a single runaway regular expression inside a firewall rule took Cloudflare's entire global network offline for 27 minutes in July 2019, spiking CPU usage to nearly 100% across every server handling HTTP and HTTPS traffic worldwide.2 Every evaluation here runs inside a background Web Worker with a hard 500 millisecond budget, and if the worker has not answered by then, the tool terminates the whole thread and reports a timeout instead of letting your tab freeze.3 That termination is the same signal a vulnerable pattern would give a production server, except here it costs you nothing but a warning banner.

Pasting (a+)+$ against roughly thirty repeated "a" characters followed by one "!" reliably triggers that 500ms cutoff, which is exactly the demonstration you want: proof the pattern is dangerous, produced safely, inside a sandboxed tab instead of a live request handler. Adding or removing a single repeated character from that test string and re-running the pattern shows the cutoff is not a fluke; the timeout appears reliably across a range of input lengths once the underlying structure is genuinely exponential.

Rewriting a vulnerable pattern once you have found one

Finding the vulnerability is only half the job. The fix, in most cases, is removing the nested quantifier structure that creates the ambiguity in the first place, since a single quantifier applied once, rather than a quantifier wrapped inside another quantifier, gives the engine exactly one way to consume each character instead of many.4

(a+)+ becomes a+ when the inner group serves no purpose beyond repeating what the outer group already repeats, and that simplification often preserves the intended matching behavior while eliminating the exponential blow-up entirely.4 Where the nested structure was doing real work, rewriting alternation to be mutually exclusive, so the engine cannot try more than one branch for the same input, achieves the same effect with a more complex pattern.

Proving the rewrite actually removed the risk

Re-testing the rewritten pattern against the same adversarial input, the run of repeated characters that broke the original, confirms the fix actually worked rather than just looking safer on inspection. If the rewritten version returns a result instantly where the original hit the 500ms ceiling, you have direct evidence the exponential path is gone, not just a pattern that reads as more cautious to a human eye.

Keep the original vulnerable pattern and its adversarial test string around somewhere after the fix ships, even in a code comment or a commit message. The next time someone edits that same pattern, re-running it against the exact input that once broke it is a faster regression check than re-deriving from scratch which quantifier combination was dangerous in the first place.

Building a habit of testing before a pattern ships

Nested quantifiers are not always obvious from reading a pattern once, especially inside a larger expression built up over several edits. A pattern that started simple can accumulate a vulnerable structure gradually, one seemingly reasonable addition at a time, until a group inside a group inside a group is quietly waiting for the right input to expose it. On a backtracking engine, the runtime of such a pattern grows exponentially with the length of the adversarial run, so adding a single repeated character roughly doubles the evaluation time.5

Making adversarial input part of your normal test pass

Treat testing against a deliberately hostile input, a long run of a single repeated character, as a normal part of validating any new pattern before it ships, not an exotic step reserved for security review. It costs one paste and takes under a second to run either way: instant confirmation the pattern is safe, or a clear timeout warning telling you exactly which pattern needs a rewrite before it goes anywhere near a request handler.

That habit is cheap precisely because the tool already isolates the risk for you. Nothing about testing a genuinely dangerous pattern here can affect anything outside the worker thread that gets terminated the moment the budget runs out, which is exactly the isolation a production request handler rarely gets by default.

When to use this

Use this guide any time you need to check a pattern for catastrophic backtracking before it ships, especially one built from nested or overlapping quantifiers.

Examples

A classic vulnerable pattern tested against an adversarial input

Before
Pattern: (a+)+$
Test text: aaaaaaaaaaaaaaaaaaaaaaaaaaaaaa!
After
The evaluation hits the 500ms budget and the tool reports a timeout instead of freezing the tab.

Rewriting the pattern to a+ removes the nested quantifier and the timeout disappears on the same input.

Sources
  1. 1.

    OWASP Foundation, "Regular expression Denial of Service - ReDoS," owasp.org, accessed August 2026. https://owasp.org/www-community/attacks/Regular_expression_Denial_of_Service_-_ReDoS

  2. 2.

    John Graham-Cumming, "Details of the Cloudflare outage on July 2, 2019," blog.cloudflare.com, July 2019. https://blog.cloudflare.com/details-of-the-cloudflare-outage-on-july-2-2019

  3. 3.

    MDN Web Docs, "Worker: terminate() method," developer.mozilla.org, accessed August 2026. https://developer.mozilla.org/en-US/docs/Web/API/Worker/terminate

  4. 4.

    Jan Goyvaerts, "Runaway Regular Expressions: Catastrophic Backtracking," regular-expressions.info, accessed August 2026. https://www.regular-expressions.info/catastrophic.html

  5. 5.

    "ReDoS," Wikipedia, accessed August 2026. https://en.wikipedia.org/wiki/Catastrophic_backtracking

FAQ

It means the pattern most likely triggered catastrophic backtracking, where nested or overlapping quantifiers force the engine to try an enormous number of ways to split the input. The tester runs every evaluation in a background thread with a 500 millisecond budget and terminates the thread if that budget runs out, so your tab never freezes even when the pattern genuinely would have.

If it times out against a crafted adversarial input here, the same input would very likely cause the same exponential slowdown wherever that pattern runs, including a live server. Rewrite the pattern to remove the nested quantifier structure rather than assuming the risk is specific to this tool.

Yes. Paste the same adversarial input you used to trigger the original timeout against your rewritten pattern. A version that returns a result instantly instead of hitting the 500ms ceiling is direct evidence the exponential backtracking path is gone.

No, but many do, and identifying which ones are safe requires understanding exactly how the engine explores ambiguous paths. Testing against a long, repetitive adversarial input is a faster and more reliable way to find out than reasoning about the pattern structure alone.

CapyToolkit doesn't upload the pattern or test text anywhere, so you can safely test a pattern pulled from real, sensitive production code without pasting it into a third-party service. The Web Worker sandbox also means a genuinely catastrophic pattern can only ever freeze its own background thread, never the page you're looking at.