ReDoS: how one regular expression can stall a server

Some regular expressions take exponential time on input that almost matches. In Node.js, where one event loop serves every request, one such match stops the whole server.

Example

Vulnerable
const BANK_ROUTING = /([0-9]+)+\#/;

app.post("/profile", (req, res) => {
  if (!BANK_ROUTING.test(req.body.bankRouting)) return res.sendStatus(400);
  save(req.body);
});
Fixed
const BANK_ROUTING = /^[0-9]{1,17}#$/;

app.post("/profile", (req, res) => {
  const value = String(req.body.bankRouting).slice(0, 32);
  if (!BANK_ROUTING.test(value)) return res.sendStatus(400);
  save(req.body);
});

How it happens

A repeated group whose contents can themselves repeat — (a+)+, ([0-9]+)+, (\w+\s?)+ — gives the engine many ways to split the same input. When the overall match fails, it tries all of them. Thirty digits followed by a character that breaks the match can take seconds; forty can take minutes. Compiling a pattern from user input (new RegExp(req.query.q)) hands the attacker the pattern itself.

How to fix it

Make each repetition unambiguous.

  • Remove the nested quantifier: ([0-9]+)+ becomes [0-9]+
  • Anchor the pattern and bound repetition counts ({1,17})
  • Cap input length before matching
  • Escape user input before building a pattern, or compare strings instead

How RepoVerse finds it

  • HighRequest input is matched against a backtracking-prone regular expression

    Nested quantifiers backtrack exponentially. A few dozen crafted characters pin the CPU, and in Node's single event loop that stalls every request on the server.

    data flow · CWE-1333

  • ModerateRequest input is compiled into a regular expression

    The caller supplies the pattern itself, including one that backtracks forever.

    data flow · CWE-1333

  • LowRegular expression that can be made to hang

    Nested quantifiers like `(a+)+` backtrack exponentially, so a crafted input of a few dozen characters pins a CPU core.

    pattern · CWE-1333

Questions

Is every nested quantifier dangerous?
No. If each repetition must start with a fixed character the rest of the group cannot match — (\.\d+)* in a version number — there is only one way to split the input and matching stays linear. RepoVerse tells these apart instead of flagging every nested group.
Why is ReDoS worse in Node.js?
Regular expressions run on the main thread, so a slow match blocks every other request on the server until it finishes.

Related

Updated 2026-10-11