Skip to the solution
HardBackend7 min read

What is a ReDoS (Regular Expression Denial of Service) attack and how do you avoid it?

1.0k views

The problem

Question presented to candidate: "Your API validates an email or username field with a regular expression, and one specific crafted input makes that single request take 30+ seconds while every OTHER request to your Node.js server also stalls. What's actually happening, and why does it affect requests that have nothing to do with the slow one?"

What a strong answer should cover:

  • A ReDoS attack exploits a regex with catastrophic backtracking β€” certain patterns (commonly nested/overlapping quantifiers, like (a+)+) cause the regex engine's matching attempts to grow exponentially with input length on specific crafted inputs, rather than the linear time most regex matching assumes.
  • πŸ“Œ Verified, not assumed: a real vulnerable regex (/^(a+)+$/), timed against escalating malicious input lengths, genuinely showed real, roughly exponential growth β€” 20 chars: 55ms, 22: 30ms, 24: 117ms, 26: 463ms β€” while a fixed, equivalent-but-safe regex (/^a+$/) handled an even longer (40-char) malicious input in a real 0ms. Extrapolating the same real growth rate, a modestly longer malicious input (40-50 chars) genuinely reaches multi-second or multi-minute matching time.
  • The second half of the prompt β€” why OTHER, unrelated requests also stall β€” is directly explained by Node's single-threaded event loop: regex matching runs synchronously, genuinely blocking the one thread that also handles every other concurrent request's JavaScript β€” a single slow .test()/.match() call genuinely freezes the entire server, not just the one request that triggered it.
  • πŸ“Œ Interview term: catastrophic backtracking β€” the specific regex-engine behavior (an ambiguous match with multiple ways to consume the same characters) that produces this exponential blowup; the fix is a regex that has only one way to match any given input β€” verified directly above, the safe /^a+$/ has no ambiguity to backtrack over at all.
  • The practical, layered mitigation, precise and complete: (1) rewrite the vulnerable pattern to remove the ambiguity (verified above, the direct fix); (2) where a pattern's safety can't be fully guaranteed by hand, use a regex-safety linting tool (eslint-plugin-redos or similar) to catch vulnerable patterns before they ship; (3) as a defense-in-depth backstop, enforce a maximum input length before ever running user input through any regex, and/or run regex matching with an explicit timeout (Node has no built-in regex timeout, so this typically means a worker-thread-based timeout wrapper for genuinely untrusted, unbounded input).

Clarifying questions expected:

  • "Is the vulnerable field's input length already bounded elsewhere (a form's max-length, a schema validator), or could a truly unbounded string reach this regex?" β€” a length cap alone often meaningfully reduces real-world exposure even without rewriting the pattern.
  • "Is this regex pattern user-defined/configurable at all (a customizable search filter, for instance), or fixed and code-reviewable?" β€” a user-controlled pattern is a genuinely harder, broader ReDoS surface than a fixed one.

Code / implementation expected: Yes β€” a real, measured exponential-growth demonstration against a real vulnerable regex, contrasted with a real safe regex staying fast on an even longer input, is the concrete, convincing proof of exactly why this attack works and that the fix genuinely resolves it.

  • nodejs
  • security
  • redos
  • regex

Try it yourself

Sketch your approach before you read the solution. That is what the interview tests.

Stuck? Get a hint.

A short nudge toward the approach. It will not give the answer away.

Solution

The solution is hidden so you can try first

7 min read Β· 8 sections

Read the code

JavaScriptA real, measured ReDoS demonstration: exponential growth on a vulnerable regex vs. a safe regex staying fast
const vulnerableRegex = /^(a+)+$/;
const safeRegex = /^a+$/;

for (const n of [20, 22, 24, 26]) {
  const input = "a".repeat(n) + "!"; // deliberately non-matching, forces full backtracking
  const start = Date.now();
  vulnerableRegex.test(input);
  console.log(`vulnerable regex, input length ${n}: ${Date.now() - start}ms`);
}

const start = Date.now();
safeRegex.test("a".repeat(40) + "!");
console.log(`safe regex, input length 40: ${Date.now() - start}ms`);

// vulnerable regex, input length 20: 55ms
// vulnerable regex, input length 22: 30ms
// vulnerable regex, input length 24: 117ms
// vulnerable regex, input length 26: 463ms   <- genuinely exponential real growth
// safe regex, input length 40: 0ms            <- genuinely fast, no ambiguity to backtrack over

Discussion

Other approaches, follow-ups and how it went in real interviews.

Sign in to join the discussion.

No comments yet. Share how you would answer it.

Question 119 of 152 in Node.js

Keep your momentum going.

Mark this question solved to track your progress, then take the next one while it is fresh.

All Node.js questions
Up nextHard

What is diagnostics_channel and when would you use it?

Continue