Test for regular expression denial of service by listing every regex that touches untrusted input, then feeding each one long strings that almost match and timing the result. Prevent it by capping input length first, rewriting ambiguous patterns, and using a linear-time engine or a timeout where your platform offers one.
Why is Node especially exposed?
Backtracking regex engines, including the one in JavaScript, can take exponential time on certain patterns. OWASP calls these "evil regex": a repeated group that itself contains repetition, or alternation with overlapping branches. Its examples include (a+)+$ and ([a-zA-Z]+)*$.
Node adds a second problem. JavaScript runs your handlers on one thread. A regex that takes ten seconds on one request does not slow that one request. It blocks the event loop, so every other connection on the process waits. One cheap request can stall a whole instance.
What does a vulnerable route look like?
A constructed example. A signup handler validates a username with this pattern, intending to allow letters separated by single dots:
const USER = /^([a-z]+\.?)+$/;
Input such as a long run of lowercase letters followed by one invalid character, like a trailing exclamation mark, forces the engine to try the many ways of splitting the letters across the group before it gives up. Each extra letter roughly doubles the work. At around 30 characters the process can stall for seconds. The pattern looks harmless in review, which is why testing matters.
How do you find candidates?
Search the code for regex literals, new RegExp(, and the validation libraries your framework uses. Include dependencies: many ReDoS advisories are in parsers, validators and URL or header handling inside packages you installed.
Mark each regex by where its input comes from. Anything fed by request bodies, headers, query strings, file names or uploaded text is in scope. Anything applied only to constants is not.
Run a static checker for super-linear patterns as a first pass, and treat its findings as leads. Then confirm by measurement.
How do you test a suspect pattern?
Build inputs from a valid prefix, many repetitions of the repeating part, then a character that breaks the match. Time the match at lengths 10, 20, 30 and 40. Linear growth is fine. Doubling per step is the signature of exponential behavior.
Put this in a unit test with a time budget. A test that fails when a match on a 5,000 character hostile string takes more than a few milliseconds will catch regressions when someone edits the pattern next year.
How do you fix it?
Order matters.
- Cap input length before the regex runs. A username has no business being 10,000 characters, and a cap turns exponential growth into a bounded cost.
- Remove ambiguity. Rewrite the pattern so each character can be matched in only one way. For the username above,
^[a-z]+(\.[a-z]+)*$says the same thing without the nested ambiguity. - Avoid nested quantifiers and overlapping alternation in anything that sees untrusted data.
- Use a linear-time engine where available. Some platforms offer one: .NET has
RegexOptions.NonBacktracking, and it sets a match timeout option too. Node has no built-in per-regex timeout, so for risky cases run the match in a worker thread you can terminate, or use a library based on a linear algorithm. - Never build a regex from user input. If search must accept patterns, escape the text or use a separate engine with limits.
What to do
- Inventory regexes that touch untrusted input, in your code and dependencies.
- Add length limits at the edge of each route.
- Rewrite nested or overlapping patterns.
- Add timed tests with hostile inputs.
- Run a static ReDoS checker in CI.
- Keep dependencies patched, since many advisories sit in transitive packages.
- Set an alert on event loop delay so a stall is visible in production.
Safeguard's dependency scanning flags known ReDoS advisories in the packages you install, and reachability analysis helps show whether your code calls the affected function.
Quick check
Is a long timeout enough protection?
No. A timeout limits one match, but an attacker can send many requests. Length caps and safer patterns reduce the cost per request to something you can absorb.
Does a web application firewall solve ReDoS?
It may block some payloads, but it does not fix the pattern. The slow regex remains reachable through any input the firewall does not recognize.
Sources
- OWASP: Regular expression Denial of Service (ReDoS)
- CWE-1333: Inefficient Regular Expression Complexity
- Microsoft: Backtracking in .NET regular expressions
Last reviewed: 18 October 2026.