LangChain

MEDIUM CWE-400 · No public advisory assigned

grep_search Python Fallback Passes User-Controlled Regex to re Module Without a Timeout

langchain-community (pip)
file_search.py — _python_search()
CWE-400
Yes (regular user)
2026-10-23
Closed as duplicate

LangChain’s grep_search tool, implemented in FilesystemFileSearchMiddleware, has a Python fallback code path used when ripgrep is unavailable or times out. That fallback reads each file line-by-line and calls re.search(pattern, line) where pattern is the raw string supplied by the agent or user. Python’s re module uses a backtracking NFA engine with no built-in timeout. A single crafted pattern causes catastrophic backtracking, saturating a CPU core and blocking the process indefinitely.

01 Root Cause

The grep_search tool accepts a pattern argument from callers. Before executing, file_search.py runs a syntax check via re.compile(pattern) — but this only validates that the regex is syntactically valid, not that it is safe to evaluate. A pattern like (a+)+b compiles without error.

When ripgrep is not present on the host, or when it exits with a non-zero return code, execution falls through to _python_search(). That function iterates over the lines of each matching file and evaluates the pattern against every line with no timeout:

# file_search.py (simplified) -- vulnerable path pattern_re = re.compile(pattern) # syntax-only; no complexity check # _python_search() — fallback used when ripgrep is unavailable for line in file_lines: if pattern_re.search(line): # no timeout; NFA backtracks indefinitely matches.append(line)

Three conditions independently trigger this path: the caller sets use_ripgrep=False; the rg binary is not found on PATH; or ripgrep exits with a timeout or error.

02 Reproduction

Step 1 — Confirm the syntax check passes silently:

import re re.compile("(a+)+b") # no exception; syntax is valid

Step 2 — Confirm _python_search() hangs:

import re pattern = re.compile("(a+)+b") victim = "a" * 35 # 35 'a' chars; no trailing 'b' # Without a timeout this call does not return. # Python's re NFA explores 2^35 partial states before concluding no match. pattern.search(victim) # hangs 30+ seconds; CPU @ 100%

Step 3 — Trigger via the agent tool interface:

# Tool call JSON sent to an agent with grep_search in its toolset: { "tool": "grep_search", "input": { "pattern": "(a+)+b", "path": "/workspace" } } # Create a trigger file so _python_search() has at least one line to evaluate: echo "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" > /workspace/trigger.txt

The agent worker thread blocks on the re.search() call. The process does not return to the caller, and all concurrent requests routed to the same worker are denied.

03 Impact

Any LangChain deployment that exposes the grep_search tool to external or untrusted agents is vulnerable. The attack requires only the ability to invoke the tool with a chosen pattern argument — a capability that any agent user in the deployment holds.

A single malicious call permanently hangs the worker thread. On single-threaded or limited-concurrency deployments this is a complete denial of service requiring a manual process restart. No data is exfiltrated and no files are modified; the sole impact is availability.

04 Recommended Fix

Replace the re module with the regex library, which supports a timeout parameter on match operations. Raise an exception on timeout rather than blocking indefinitely:

# _python_search() — safe replacement import regex # pip install regex try: pattern_re = regex.compile(pattern) except regex.error: raise ValueError("Invalid regex pattern") for line in file_lines: try: if pattern_re.search(line, timeout=5): # 5-second hard limit per line matches.append(line) except TimeoutError: raise ValueError("Pattern exceeded complexity limit")

Reported to LangChain. Received thanks; closed as duplicate. No formal security advisory was published.