How Lodash escapeRegExp Prevents Regex Injection

This article explains how the Lodash utility function _.escapeRegExp neutralizes security vulnerabilities and logic errors when creating dynamic regular expressions from untrusted input. By converting functional regular expression syntax into harmless literal characters, the method eliminates threats such as Regular Expression Denial of Service (ReDoS) and unintended pattern execution.

When applications dynamically construct regular expressions using user input via the new RegExp(userInput) constructor, any reserved regular expression characters included in that input are interpreted by the JavaScript engine as functional operators. These characters include ^, $, \, ., *, +, ?, (, ), [, ], {, }, and |. If an attacker supplies inputs containing nested quantifiers—such as (a+)+$—or broad match-all wildcards, they can manipulate the query logic or trigger catastrophic backtracking, causing the server's event loop to freeze in a ReDoS attack.

Lodash's _.escapeRegExp completely nullifies this threat by intercepting the raw string before it reaches the RegExp constructor. Internally, the function uses a targeted replacement pattern that identifies every standard regular expression metacharacter:

const reRegExpChar = /[\\^$.*+?()[\]{}|]/g;
const reHasRegExpChar = RegExp(reRegExpChar.source);

function escapeRegExp(string) {
  return string && reHasRegExpChar.test(string)
    ? string.replace(reRegExpChar, '\\$&')
    : string || '';
}

The function checks the string against reHasRegExpChar for performance optimization. If special characters are detected, it applies a global replacement where each matched metacharacter is prepended with an escaping backslash (\\$&).

By prefixing each functional descriptor with a backslash, the JavaScript regular expression engine's lexical analyzer changes how it tokens the string:

Consider an unescaped search implementation:

// Vulnerable implementation
const search = "(a+)+";
const regex = new RegExp(search); 
// Compiles to: /(a+)+/ (vulnerable to catastrophic backtracking)

Applying _.escapeRegExp neutralizes the logic:

// Secure implementation with Lodash
const search = "(a+)+";
const safeSearch = _.escapeRegExp(search); 
// Produces: "\\(a\\+\\)\\+"

const regex = new RegExp(safeSearch); 
// Compiles to: /\(a\+\)\+/ (matches only the literal string "(a+)+")

Because every metacharacter is reduced to its literal character equivalent, dynamically nested descriptors lose all operational syntax. The regular expression engine parses the sanitized string as a linear sequence of static characters, reducing the computational complexity of the evaluation from exponential time to deterministic, linear time (\(O(n)\)), completely mitigating regex injection.