Skip to content
Injection Reviewed 2026-09-13

Regular Expression Injection

What does this mean ?

Regular expression injection occurs when untrusted text is interpreted as pattern syntax even though the application intended it as a literal value. The altered pattern can match unexpected input. Separately, a costly pattern or oversized input can cause excessive processing; this can happen even with a developer-written pattern.

What can happen ?

A search may return unintended results or a validation rule may be bypassed. Backtracking engines can spend excessive time on particular pattern/input combinations and reduce service availability. Other engines offer different complexity guarantees, so do not assume every regex implementation uses the same matching algorithm.

Recommendation

First decide whether the feature accepts literal text or an actual regular expression. For literal substring search, use the language's string search API. If a literal must be embedded in a larger trusted regex, use the engine's pattern-escaping function in the intended context. Pattern escaping is not the same as escaping a replacement string, and it changes a user-authored regex into literal text.

For an intentional regex feature, constrain pattern and input size, supported syntax and execution resources. Use a suitable engine with known complexity properties where possible. .NET supports explicit match timeouts; Python's standard re and JavaScript's standard RegExp do not provide the same per-match timeout API. An enforced isolated-worker deadline may be needed; merely timing a call without stopping its execution does not bound CPU use. A heuristic pattern analyzer is useful review assistance, not proof that a pattern is safe.

Sample Code

The feature below searches for a literal substring. Inputs are bounded to 4,096 language string units for the text and 1–128 for the needle. These are illustrative limits; byte, code-point and UTF-16 length measurements differ by language. Request-body limits must apply before large inputs are allocated.

The unsafe alternative is to compile the untrusted needle as a regex, such as new RegExp(needle).test(text) or re.search(needle, text).

def contains_literal(text: str, needle: str) -> bool:
    if not isinstance(text, str) or not isinstance(needle, str):
        raise ValueError("Text and needle must be strings")
    if len(text) > 4096 or not 1 <= len(needle) <= 128:
        raise ValueError("Search input outside limits")
    return needle in text

For a trusted pattern that needs a literal value, Python provides re.escape. It does not impose an execution timeout on the surrounding expression.

function containsLiteral(text, needle) {
  if (typeof text !== 'string' || typeof needle !== 'string' ||
      text.length > 4096 || needle.length < 1 || needle.length > 128) {
    throw new Error('Invalid search input');
  }
  return text.includes(needle);
}

includes performs literal search and does not interpret regex operators.

static bool ContainsLiteral(string text, string needle)
{
    if (text is null || needle is null || text.Length > 4096 ||
        needle.Length < 1 || needle.Length > 128)
        throw new ArgumentException("Invalid search input");
    return text.IndexOf(needle, StringComparison.Ordinal) >= 0;
}

If a regex is required, use Regex.Escape for the literal component and an explicit match timeout for the full expression. A timeout failure must become a controlled result, not an unbounded retry.

static boolean containsLiteral(String text, String needle) {
    if (text == null || needle == null || text.length() > 4096 ||
            needle.length() < 1 || needle.length() > 128) {
        throw new IllegalArgumentException("Invalid search input");
    }
    return text.contains(needle);
}

Java's Pattern.quote is for a literal component when regex composition is unavoidable.

PHP 8+, with strings supplied by an input schema:

function containsLiteral(string $text, string $needle): bool {
    if (strlen($text) > 4096 || strlen($needle) < 1 || strlen($needle) > 128) {
        throw new InvalidArgumentException('Invalid search input');
    }
    return str_contains($text, $needle);
}

For a regex, preg_quote($needle, $delimiter) needs the chosen delimiter and a complete surrounding pattern. It cannot be passed to preg_grep as though it were a fully delimited expression.

def contains_literal(text, needle)
  unless text.is_a?(String) && needle.is_a?(String) &&
         text.length <= 4096 && (1..128).cover?(needle.length)
    raise ArgumentError, 'Invalid search input'
  end
  text.include?(needle)
end

Ruby's pattern-escaping API is Regexp.escape, not Regex.escape. Current Ruby versions also provide regex timeout controls; check the runtime version and semantics before depending on them.

Regression checks

Test literal punctuation such as a.b, [name], *, parentheses and backslashes. a.b must not match acb unless that literal substring exists. Verify maximum lengths, empty needles and invalid types. For any intentional regex feature, run separate bounded local complexity tests under an enforced deadline; do not execute intentionally expensive patterns in a production request handler to test it.

References