Regular expressions are a double-edged sword. They are one of the most powerful tools in a developer's arsenal for validating input, parsing text, and matching patterns. But they are also one of the most dangerous. A poorly written regex can validate user input reliably — or it can bring your entire server to its knees with a single malicious string. This is the story of patterns that protect and patterns that kill.
The ReDoS Threat
Regular Expression Denial of Service (ReDoS) is a security vulnerability where an attacker crafts input that causes a regex engine to take an exponentially long time to evaluate. The root cause is catastrophic backtracking — when the regex engine tries exponentially many paths through the input string trying to find a match.
The classic example is a regex like /^(a+)+$/ evaluated against the string "aaaaaaaaaaaaaaaaaaaaaab". The engine tries every possible way to partition the "a" characters into groups before finally concluding there is no match. With 20 "a" characters, this takes milliseconds. With 30, it takes seconds. With 40, it takes hours. With 50, it takes years.
Identifying Dangerous Patterns
Dangerous regex patterns share common characteristics:
- Nested quantifiers: Patterns like
(a+)+,(a*)*, or(a|a)+where a quantified group contains another quantifier. - Overlapping alternatives: Patterns like
(a|a)where multiple alternatives can match the same input. - Complex backreferences: Patterns that require the engine to track and revisit many possible matches.
These patterns are surprisingly common in production code. Studies have found ReDoS vulnerabilities in the regex patterns of major frameworks, libraries, and applications — including Node.js, Ruby on Rails, and many popular npm packages.
Safe Regex Practices
To write safe regex patterns, follow these principles:
- Avoid nested quantifiers: Replace
/^(a+)+$/with/^a+$/— they match the same strings but the latter is linear-time. - Use possessive quantifiers: If your regex engine supports them, possessive quantifiers (
a++,a*+) prevent backtracking. - Use atomic groups: Atomic groups (
(?>...)) lock in the first match found, preventing backtracking into the group. - Set time limits: Many regex engines allow you to set a timeout. Use this as a safety net.
- Test with adversarial input: Before deploying a regex, test it against strings designed to trigger catastrophic backtracking.
Test Locally, Deploy Confidently
When testing regex patterns, especially for security-critical input validation, use a client-side tool. Online regex testers receive your patterns and test strings on their servers — which means your validation logic and potentially sensitive test data are exposed. A client-side regex tester keeps your patterns and data completely local.