Regular Expression Denial of Service (ReDoS) in JS & PHP: Spotting Catastrophic Backtracking
Here is a pattern I keep finding in slug and username validators: /^([a-z0-9]+-?)+$/ . It looks harmless and passes every unit test. Now send it thirty lowercase letters followed by an underscore. On Node 24, that single test() call ran for about 6.5 seconds on my machine. Node runs your JavaScript on one event-loop thread, so every other request on that process waits for it. A handful of those…
ReDoS is a type of denial-of-service attack that exploits vulnerabilities in regular expression engines. In JavaScript and PHP, it occurs when a regex engine, such as V8's Irregexp in Node.js or PCRE2 in PHP's preg_* functions, goes into catastrophic backtracking while trying to match an input string. This happens when the pattern contains ambiguous quantifiers, leading to exponential or polynomial growth in the number of possible matches.
To spot catastrophic backtracking, look for nested or overlapping quantifiers in your regex patterns. For example, the pattern /^([a-z0-9]+-?)+$/ can be split into various combinations, resulting in a large number of potential matches. You can prove backtracking by crafting near-miss inputs and timing the execution of the regex test() call. The time taken will grow exponentially with the length of the input string if the engine is backtracking catastrophically.
Five pattern smells to watch out for are:
1. Nested quantifiers: Patterns with both inner and outer + quantifiers, like /^([a-z0-9]+-?)+$/
2. Overlapping alternation: Alternating character classes within a repeated group, like /A/B+/A+/
3. Polynomial exponential growth: Patterns with competing quantifiers for the same characters, like /^\w+\w*@/ or /\d+\.?\d*/
4. Adjacent overlapping repeats: Patterns with two quantifiers that can compete for the same characters, like /^\w+\w*/
5. Quadratic tail: Unanchored greedy quantifiers at the end of the pattern, like /\s+$/
To fix ReDoS vulnerabilities, rewrite your regex patterns to be unambiguous, ensuring that each character can only be consumed in one way. For example, the vulnerable slug pattern /^( [a-z0-9] +-?)+$/ can be rewritten as /^(?=[a-z0-9]*$)[a-z0-9]+(-[a-z0-9]+)*$/ to make it safe from catastrophic backtracking. Additionally, consider using tools like ESLint's regexp/no-super-linear-backtracking rule and recheck to automatically detect potential ReDoS issues in your code.
Written by urgent.news from Dev.to's reporting — not their text. Machine-written — may contain errors; check the original before relying on it.