Regular Expression With Catastrophic Backtracking

ID

javascript.regex_catastrophic_backtracking

Severity

high

Remediation Complexity

medium

Remediation Risk

low

Remediation Effort

medium

Resource

Efficiency

Language

JavaScript

Tags

CWE:1333, efficiency, regex, reliability

Description

Reports a regular expression whose structure lets the matcher split the same input in an explosive number of ways. A subject that almost matches then takes seconds or minutes where a match takes microseconds.

Two shapes are reported, both decided from the pattern’s own token stream:

  • nested repetition — a group whose whole body is one repeated atom, itself repeated with no upper bound: (a+)+, (\d*)*, ([a-z]?)+. Every character can be attributed to the inner or to the outer repetition, so the number of splits grows exponentially with the length of the subject.

  • adjacent repetition over overlapping sets — two unbounded repetitions side by side whose character sets provably contain one another: \d+\w+, \s*\s*, [a-z]+[a-z0-9]*. Every boundary between them is a candidate split, which is quadratic rather than exponential but still enough to stall a request.

Both are judged on the pattern alone, wherever the subject comes from, so a pattern applied to input that is trusted today is reported too.

Rationale

// Bad
const IDENTIFIER = /^(\w+)+$/;

IDENTIFIER.test("aaaaaaaaaaaaaaaaaaaaaaaaaaa");   // matches at once
IDENTIFIER.test("aaaaaaaaaaaaaaaaaaaaaaaaaaa!");  // never returns in practice
  • The inner \w+ and the outer + compete for the same characters. With the trailing ! no split can ever match, and the matcher tries all of them — roughly two to the power of the subject’s length.

  • The cost is paid on the failing input, which is the one an attacker controls and the one a validation regex sees most.

  • Nothing about the pattern looks slow. It passes every test written with matching input, and the first evidence is a request that stops responding.

Remediation

Repeat a unit that cannot be split more than one way, or bound the outer repetition.

// Good
const IDENTIFIER = /^\w+$/;          // the outer repetition was redundant

const CSV_ROW = /^(\w+,)+$/;         // each repetition must end with a comma
const SHORT_RUN = /^(\w+){1,8}$/;    // bounded, so the search cannot explode

For two adjacent repetitions, merge them or make the character sets disjoint:

// Good
const CODE = /^\w+$/;                // was /^\d+\w+$/
const PLATE = /^\d+[a-z]+$/;         // the two sets cannot claim the same character

© 2026 Xygeni. All rights reserved