0x55aa
← Back to Blog

ReDoS: Your Innocent Email Validator Just Took Down Production šŸ’„

•6 min read

ReDoS: Your Innocent Email Validator Just Took Down Production šŸ’„

If someone told you that the regex you wrote to validate email addresses could take down your entire Node.js server with a single crafted input, you'd probably laugh. Then cry. Then laugh again because it's happened to me.

Welcome to ReDoS — Regular Expression Denial of Service — the vulnerability that proves even your most defensive code can be weaponized against you. šŸŽÆ

What Even Is ReDoS? šŸ¤”

Here's the deal. Most developers know regex is "slow" in some vague hand-wavy sense. But ReDoS is a specific, exploitable flavor of slow.

It works because of something called catastrophic backtracking. When a regex engine tries to match a pattern and fails, it backtracks — it tries different combinations of how the pattern could have matched. Some regex patterns, given carefully crafted input, cause the engine to explore an exponential number of combinations before giving up.

The analogy I use in security communities: imagine a lock with 10 dials. If you have to try every combination before concluding "wrong key," that's 10^10 attempts. Now imagine your lock is JavaScript's regex engine and the hacker designed the key specifically to make you try everything.

Result: one request, one crafted string, your event loop grinds to a halt.

The Classic Example That'll Make You Wince 😬

// This pattern looks reasonable, right?
const emailRegex = /^([a-zA-Z0-9]+)*@[a-zA-Z0-9]+\.[a-zA-Z]+$/;

// Perfectly fine input
emailRegex.test("[email protected]"); // āœ… fast

// Evil input from a hacker
emailRegex.test("aaaaaaaaaaaaaaaaaaaaaaaa!"); // šŸ’€ hangs forever

See ([a-zA-Z0-9]+)*? That outer * and inner + create overlapping matches. The engine tries every possible way to group those a's before concluding the @ never appears. Each extra character doubles the work. Add 30 a's and you're looking at 2^30 attempts — over a billion operations.

In my experience building production systems, I've seen Node.js processes pin a CPU core to 100% for minutes on a single regex match. One process. One request. Dead server.

Real Production ReDoS Vulnerabilities 🚨

This isn't theoretical. These hit real libraries:

  • moment.js had a ReDoS in date parsing (CVE-2022-31129). Date validation. Who'd have thought?
  • validator.js — the super popular npm package — had multiple ReDoS CVEs over the years
  • ua-parser-js had one in user-agent parsing
  • Cloudflare went down in 2019 because a WAF regex caused catastrophic backtracking. Cloudflare. Down. Because of regex. This is the world we live in.

As someone passionate about security, I love pointing to that Cloudflare incident. If the company whose entire business is protecting web infrastructure got taken out by a regex, your email validation isn't safe either.

Spotting Vulnerable Patterns šŸ”

The culprit is almost always nested quantifiers on groups that can match the same characters:

āŒ Dangerous patterns:
(a+)+        → nested quantifiers
(a|aa)+      → alternation with overlap
([a-z]+)*    → group with * outside, + inside
(a*b?)*      → multiple optional parts in a loop

āœ… Safer alternatives:
a+           → simple quantifier, no nesting
[a-z]+       → character class, not a group

Pro Tip: Run your regex through regex101.com and look for the "catastrophic backtracking" warning. Tools like safe-regex (npm) and vuln-regex-detector will flag dangerous patterns automatically.

The Fix: Practical Approaches šŸ›”ļø

Option 1: Simplify Your Regex

// āŒ Vulnerable — nested quantifiers
const badEmail = /^([a-zA-Z0-9]+)*@([a-zA-Z0-9]+\.)+[a-zA-Z]{2,}$/;

// āœ… Better — no nested quantifiers
const betterEmail = /^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$/;

Option 2: Use a Regex Timeout (Node.js)

Node.js 19+ has a built-in timeout for RegExp:

// Timeout after 100ms — won't hang your event loop
try {
  const regex = /^([a-zA-Z0-9]+)*@.+$/v;
  const result = regex.test(userInput);
} catch (e) {
  // Timeout or error — treat as invalid input
  console.warn('Regex timeout — possible ReDoS attempt');
}

Option 3: Use a Dedicated Validation Library

// validator.js is well-maintained and tested for ReDoS
import validator from 'validator';

// Don't reinvent the wheel
if (!validator.isEmail(userInput)) {
  return res.status(400).json({ error: 'Invalid email' });
}

Option 4: Run Regex in a Worker Thread

const { Worker, isMainThread, parentPort, workerData } = require('worker_threads');

// Isolate regex to a worker — if it hangs, kill the worker, not your server
function safeRegexTest(pattern, input, timeoutMs = 100) {
  return new Promise((resolve, reject) => {
    const worker = new Worker(__filename, { workerData: { pattern, input } });
    const timer = setTimeout(() => {
      worker.terminate();
      resolve(false); // Treat timeout as non-match
    }, timeoutMs);
    worker.on('message', (result) => { clearTimeout(timer); resolve(result); });
    worker.on('error', reject);
  });
}

Real Talk: How Attackers Actually Use This šŸ’¬

In security communities, we discuss ReDoS as a "low-effort, high-impact" attack. No authentication needed. No special tools. Just:

  1. Identify a form with regex validation (login, signup, search)
  2. Find or craft input that triggers backtracking
  3. Send requests in a loop from multiple IPs
  4. Watch your WAF logs light up like a Christmas tree — if you have monitoring

The particularly nasty part? Default WAF rules don't block it. The request looks totally legitimate. It's a valid HTTP POST to your API. Your rate limiting might not trigger (especially if the attacker throttles). The first sign something's wrong is your server slowing to a crawl.

In my experience building production systems with Laravel and Node.js backends, we added regex complexity scanning to our CI pipeline after I stumbled on a vulnerable pattern in a PR review. The dev didn't know. I barely knew. It just looked like "thorough email validation."

Your ReDoS Prevention Checklist āœ…

Before shipping that validation code:

  • Run regex through safe-regex or vuln-regex-detector
  • No nested quantifiers (a+)+, (a*b?)*, etc.
  • Use validator libraries for common formats (email, URL, phone)
  • Set regex execution timeouts in Node.js (v19+)
  • Test with long strings of repeated characters before deploying
  • Monitor CPU spikes in production — sudden 100% CPU = possible ReDoS
  • Check your npm dependencies for ReDoS CVEs with npm audit

TL;DR šŸŽÆ

ReDoS is a denial-of-service attack using carefully crafted input that causes your regex engine to spend exponential time failing to match. It's caused by nested quantifiers. It's exploitable with no authentication. And it's sitting in validation code in most codebases right now.

Your regex doesn't have to be wrong to be dangerous — it just has to be imprecise. Test your patterns, use established libraries, and for the love of all things serverless, don't write your own email regex from scratch at 2am.

Trust me on the 2am regex thing. I speak from experience.


Hit me up on LinkedIn if you've found a ReDoS in the wild — I love a good vulnerability story. More security posts over at GitHub. šŸ”