Regular expression injection happens when an application builds a regex pattern from untrusted input instead of treating that input as literal, escaped data — the same root mistake that makes SQL injection possible, except the target is a pattern-matching engine instead of a database. The fallout runs two directions. Attackers can smuggle metacharacters like .*, (a+)+, or anchor swaps into a pattern to widen or narrow what it matches, bypassing validation, authentication, or access-control checks. Or they can inject nested quantifiers that trigger catastrophic backtracking, turning one crafted string into a CPU-exhausting denial-of-service condition known as ReDoS, capable of dragging a single request from single-digit milliseconds to minutes of blocked processing. CVE-2021-3765 (nth-check), CVE-2022-3517 (minimatch), CVE-2024-45296 (path-to-regexp), and Rails' 2013 anchor-bypass CVE-2013-0155 all trace back to this pattern. Here is what security teams need to know to find it, exploit-test it, and close it.
What is regex injection?
Regex injection is when attacker-controlled input reaches the string used to construct a regular expression, letting the attacker alter the pattern's matching logic rather than just supplying data to be matched. A typical vulnerable line looks like const re = new RegExp(req.query.search) in Node.js, or Pattern.compile(request.getParameter("q")) in Java. Because regex syntax reserves characters such as ., *, +, ?, (, ), |, and ^/$ as operators, any of those left unescaped in user input changes what the resulting pattern matches. If a search feature builds new RegExp(query) and a user submits .*, every record in the dataset matches regardless of the intended filter. If an allowlist check builds ^(${allowedUser})$ from a value that itself came from user input, submitting admin|.* can make the check pass for anything.
How is regex injection different from a ReDoS bug?
Regex injection is the entry point; ReDoS is one possible outcome, not the only one. A ReDoS vulnerability can exist in a completely static, hardcoded regex that a developer wrote poorly — for example, the (a+)+$ style pattern behind CVE-2021-3765 in nth-check never touched user-supplied pattern text, only user-supplied input strings tested against a fixed, badly constructed expression. Regex injection, by contrast, requires the attacker to influence the pattern itself. The two combine when injected input both rewrites the pattern and supplies a payload string that exploits the new pattern's backtracking behavior, which is why the two get conflated. CWE-1333 (Inefficient Regular Expression Complexity) covers the ReDoS half; CWE-625 (Permissive Regular Expression) and CWE-20 (Improper Input Validation) cover the injection half.
What real-world vulnerabilities have stemmed from this pattern?
Public CVE data shows regex construction bugs shipping in some of the most widely depended-on packages in the JavaScript ecosystem. CVE-2021-3765 in nth-check (versions before 2.0.1, CVSS 7.5) let a crafted CSS selector trigger catastrophic backtracking; because nth-check is a transitive dependency of css-select, which ships inside react-scripts and svgo, the fix in nth-check 2.0.1 had to propagate through several layers of the npm dependency tree before affected apps were actually patched. CVE-2022-3517 in minimatch (versions before 3.0.5, CVSS 7.5) allowed a crafted glob pattern to hang any process that ran user-supplied strings through minimatch(). CVE-2024-45296 in path-to-regexp, the route-compiling library behind Express.js, let an attacker-controlled route parameter produce exponential backtracking; the maintainers shipped fixes across the 0.1.x, 1.x, 3.x, and 6.x branches simultaneously because so many downstream frameworks pinned different major versions. Further back, Ruby on Rails' CVE-2013-0155 (disclosed January 2013) showed the logic-bypass side: format validators built with ^ and $ anchors instead of \A and \z could be defeated by embedding a newline in the input, since ^/$ match line boundaries, not string boundaries — letting attackers submit values like "malicious\nadmin@example.com" that passed an email-format check the developer thought was strict.
Why do most regex injection CVEs score 7.5 on CVSS?
Most pure ReDoS-flavored regex injection CVEs land at exactly 7.5 (High) because they share the same CVSS 3.x vector: AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H — network-reachable, low attack complexity, no privileges or user interaction required, and impact limited to availability. That vector describes nth-check's CVE-2021-3765, minimatch's CVE-2022-3517, and path-to-regexp's CVE-2024-45296 identically, because in each case the worst outcome is CPU exhaustion, not data exposure. The math changes when the injection produces a logic bypass instead of a hang: a regex injection that lets an attacker forge an authentication token format check or bypass an SSRF allowlist trades the A:H (availability-only) impact for C:H/I:H and often PR:N still holding, which is why authentication-bypass variants of this bug class routinely score 8.1–9.8 rather than 7.5. Scanners that only track the CVE number miss this distinction; the same underlying code pattern can be a nuisance denial-of-service or a full account-takeover primitive depending on what the surrounding logic does with the match result.
How do attackers actually exploit unescaped regex input?
Attackers exploit it by submitting strings that regex metacharacters interpret as structure, not literal text, so the check that was supposed to constrain input instead gets rewritten by it. Take a login-throttling feature that whitelists internal usernames with new RegExp('^(' + allowedList.join('|') + ')$'), where allowedList includes a value taken from an admin-editable but externally-influenced config field. Submitting a username of .* as one of those list entries turns the whole check into "match anything." In a search endpoint, a query field passed straight into collection.find({ name: new RegExp(userInput) }) in a MongoDB-backed Node.js app lets an attacker submit ^(?=.*a)(?=.*b).*.*.*.*.*.*.*.*!$ — a pattern engineered for catastrophic backtracking against long input strings — pinning a CPU core at 100% for as long as the event loop stays blocked, since Node.js regex evaluation is synchronous and single-threaded by default. A single such request against an unprotected endpoint can serialize an entire Node process, since one blocked event loop stalls every other in-flight request on that instance.
How do you detect and prevent regex injection in your codebase?
Detection starts with tracing every RegExp(), Pattern.compile(), re.compile(), or equivalent constructor call back to its input source and flagging any that touch a request parameter, header, cookie, or database field without escaping. Prevention has three concrete controls. First, escape literal input before it reaches a regex constructor — lodash.escapeRegExp() in JavaScript, re.escape() in Python (standard library since 3.7), or Pattern.quote() in Java all convert metacharacters to their literal equivalents. Second, cap regex evaluation time or complexity: Node's safe-regex and redos-detector packages statically flag exponential-worst-case patterns before deployment, and swapping the default engine for Google's RE2 (linear-time, no backtracking) eliminates the ReDoS half of the risk entirely, at the cost of losing backreferences and lookaround. Third, run static analysis rules purpose-built for this class — Semgrep's javascript.lang.security.audit.detect-non-literal-regexp rule and CodeQL's js/redos and js/regex-injection queries both catch the new RegExp(untrusted) pattern directly in CI, before it merges.
How Safeguard Helps
Safeguard's reachability analysis traces whether a vulnerable regex construction path — like an unescaped RegExp() call flagged by a CVE or a Semgrep/CodeQL rule — is actually invoked with attacker-reachable input in your specific build, so security teams can triage the CVE-2021-3765-class alert that matters from the hundreds that don't. Griffin AI reviews the surrounding data flow to confirm whether user input reaches the pattern constructor unescaped, distinguishing true regex injection from safe, hardcoded expressions that merely evaluate untrusted data. SBOM generation and ingest give teams instant visibility into every transitive dependency — nth-check, minimatch, path-to-regexp, and the rest — the moment a new regex-related CVE is published, without waiting for a manual dependency audit. When a fix is available, Safeguard opens an auto-fix PR that bumps the vulnerable package to the patched version or wraps the flagged construction with an escaping call, cutting remediation from a multi-day ticket to a same-day merge.