← Alle Neuigkeiten
security5 Min. Lesezeit

ReDoS and unsafe regex patterns in AI-generated JavaScript

Von Seaworthy · 25. Juni 2026

Green text code on a black terminal screen, showing lines of programming syntax

Photo by Markus Spiske on Unsplash

ReDoS and unsafe regex patterns in AI-generated JavaScript

Regular expressions are one of those tools that look harmless until they aren't. A single crafted HTTP request. A string of about 50 characters. That's all it takes to pin a Node.js event loop at 100% CPU and make your server completely unresponsive. This is ReDoS: Regular Expression Denial of Service.

The attack isn't new. But AI coding assistants have given it a second life, because the patterns that cause catastrophic backtracking are also the patterns that look perfectly reasonable when generated from a vague prompt.

How catastrophic backtracking works

The JavaScript regex engine uses backtracking to find matches. For most inputs this is fine. For certain combinations of pattern and input, the engine explores an exponential number of paths before giving up.

The classic shape of a vulnerable pattern is nested quantifiers on overlapping character classes:

// Vulnerable: nested quantifiers on the same character set
const emailRegex = /^([a-zA-Z0-9]+)*@[a-zA-Z0-9]+\.[a-zA-Z]{2,}$/;

// To trigger it:
const evilInput = 'a'.repeat(30) + '!';
emailRegex.test(evilInput); // hangs

The ([a-zA-Z0-9]+)* part is the problem. The outer * and inner + both match [a-zA-Z0-9], so the engine can split the prefix in countless ways before deciding none of them work when it hits the !. With 30 characters, the number of paths is in the billions.

Node.js is single-threaded. While that regex is grinding, nothing else runs. No other requests are served. No health checks respond. Your load balancer marks the instance as down.

Why AI assistants produce these patterns

Ask an LLM to write an email validator and you'll often get something like the pattern above. The model has seen millions of regex examples on the internet. Many of them are vulnerable. The training data doesn't encode "this pattern has quadratic worst-case complexity" as a signal. It encodes "this pattern appears in code that people considered correct."

The same goes for URL validators, password strength checks, CSV parsers written as one-liners, and any other case where a developer asks for a quick regex without specifying performance constraints.

I've tested this across several popular assistants. The hit rate on vulnerable patterns for common validation tasks is surprisingly high, somewhere around one in three. That's not a knock on the tools. It's a reminder that AI output needs the same review any other input would get.

Patterns to flag immediately

There are a few shapes that should trigger an immediate review:

  • Nested quantifiers: (a+)+, (a*)*, ([a-z]+)*
  • Alternation inside a repeated group where alternatives overlap: (a|a)+
  • Repeated groups followed by optional matches on the same character class

Here's a safer rewrite of the email pattern above:

// Safe: no nested quantifiers, anchored, bounded length check first
function isValidEmail(input) {
  if (typeof input !== 'string' || input.length > 254) return false;
  const safe = /^[a-zA-Z0-9._%+\-]+@[a-zA-Z0-9.\-]+\.[a-zA-Z]{2,}$/;
  return safe.test(input);
}

The length check before the regex caps the input the engine ever sees. The pattern avoids nested quantifiers entirely. Neither change makes it a perfect email validator, but validation is always a heuristic and this one won't melt under load.

Automated detection

You don't have to eyeball every regex manually. The vuln-regex-detector package from the safe-regex ecosystem can check patterns statically. For CI, redos-detector and safe-regex are both worth a look as lint-step additions.

npx safe-regex '([a-zA-Z0-9]+)*@[a-zA-Z0-9]+\.[a-zA-Z]{2,}'
# => unsafe

ESLint has a no-unsafe-regex rule via eslint-plugin-security. If you're not already running that plugin on a TypeScript project, it's worth adding. It won't catch everything, but it catches the obvious cases without any effort.

The Node.js angle

Other runtimes have mitigation options. V8 (and therefore Node.js) does not have a built-in regex timeout. The engine will run until it finishes or the process is killed. That means a ReDoS vulnerability in a server-side route handler is a straight path to downtime.

The options you have are:

  1. Validate input length before applying any complex regex.
  2. Use a linear-time regex library like re2 (the Google RE2 binding for Node.js), which explicitly rejects patterns with exponential worst-case behaviour.
  3. Run regex matching in a worker thread with a timeout so a hung match doesn't take down the main thread.

The re2 approach is the most robust. The library refuses to compile unsafe patterns at startup, turning a runtime vulnerability into a build-time error.

const RE2 = require('re2');

// This throws at startup if the pattern is unsafe
const safeEmail = new RE2('^[a-zA-Z0-9._%+\\-]+@[a-zA-Z0-9.\\-]+\\.[a-zA-Z]{2,}$');

That's a meaningful shift: you find out the pattern is dangerous when you deploy, not when an attacker finds it.

What to do right now

If you're shipping JavaScript or TypeScript that accepts user input and runs it through a regex, spend an hour with safe-regex across your codebase. Pay particular attention to any validation code written or suggested by an AI assistant in the last year. The patterns are often fine. Sometimes they're not, and the difference matters in production.

Seaworthy's static analysis checks for unsafe regex patterns as part of its security and resilience scan categories.

Dieser Artikel wurde von KI generiert und fasst öffentlich verfügbare Quellen zusammen.

Von den Machern von Seaworthy

Seaworthy scannt dein Repository nach den Problemen, die in Artikeln wie diesem behandelt werden.

Sicherheitslücken, offengelegte Geheimnisse und Fehlkonfigurationen — erkannt, bevor du bereitstellst. Kostenlos, kein Konto nötig.

$ npx seaworthycode