VAITP Dataset

← Back to the dataset

CVE-2026-61438

PraisonAI RCE via crafted YAML due to insufficient AST validation.

  • CVSS 7.0
  • CWE-78
  • Input Validation and Sanitization
  • Remote

PraisonAI before 4.6.78 contains a remote code execution vulnerability in JobWorkflowExecutor._exec_inline_python() due to insufficient AST validation of workflow script steps. Attackers can create malicious YAML workflow files with import os statements followed by os.system() calls that bypass sandbox checks and execute arbitrary OS commands with process privileges.

CVSS base score
7.0
Published
2026-07-15
OWASP
A03 Injection
Orthogonal defect classification
Checking
Code defect classification
Missing Check
Category
Input Validation and Sanitization
Subcategory
Command Injection
Accessibility scope
Remote
Impact
Arbitrary Code Execution
Affected component
PraisonAI
Fixed by upgrading
Yes

Solution

Upgrade to PraisonAI version 4.6.78 or later.

Vulnerable code sample

import ast

class JobWorkflowExecutor:
    """
    A simplified representation of the vulnerable class before the fix.
    This class is intended to demonstrate the vulnerability described in
    CVE-2026-61438 (fictional CVE for demonstration purposes).
    """

    def _is_safe(self, tree):
        """
        Performs an insufficient AST validation. It only checks for a few
        specific top-level function call names, but completely fails to
        validate imports or attribute-based calls (like 'os.system').
        """
        for node in ast.walk(tree):
            # This check is flawed. It only looks for ast.Call nodes where the
            # function is a simple name (e.g., "eval(...)"). It does not
            # check for attribute calls (e.g., "os.system(...)") and does not
            # block 'import' statements.
            if isinstance(node, ast.Call) and hasattr(node.func, 'id'):
                if node.func.id in ['exec', 'eval', 'open', '__import__']:
                    return False
        return True

    def _exec_inline_python(self, code_string):
        """
        Parses and executes an inline Python script from a workflow.
        This method contains the remote code execution vulnerability.
        """
        try:
            tree = ast.parse(code_string, mode='exec')
            if not self._is_safe(tree):
                raise PermissionError("Execution of unsafe code was blocked.")

            # If the insufficient validation passes, the code is executed.
            # An attacker can bypass the check with 'import os; os.system(...)'
            # which leads to remote code execution.
            exec(compile(tree, filename="<ast>", mode="exec"), {})
        except Exception as e:
            # In a real application, this would log the error.
            # print(f"Error during execution: {e}")
            pass

Patched code sample

import ast

class JobWorkflowExecutor:
    """
    A conceptual representation of the class from the CVE description.
    """
    class _SecureAstValidator(ast.NodeVisitor):
        """
        This validator implements the fix by thoroughly inspecting the code's
        Abstract Syntax Tree (AST) for forbidden patterns. The original vulnerability
        arose from insufficient validation.
        """
        FORBIDDEN_MODULES = {'os', 'sys', 'subprocess', 'shutil', 'ctypes'}
        FORBIDDEN_BUILTINS = {'__import__', 'eval', 'exec', 'open'}
        FORBIDDEN_ATTRIBUTES = {
            'system', 'popen', 'spawn', 'call', 'run',  # Common os/subprocess functions
            '__subclasses__', '__globals__', '__code__' # Introspection/escape attributes
        }

        def visit_Import(self, node: ast.Import):
            """
            Prevents 'import os' and other dangerous modules.
            """
            for alias in node.names:
                if alias.name in self.FORBIDDEN_MODULES:
                    raise PermissionError(f"Import of forbidden module '{alias.name}' is not allowed.")
            self.generic_visit(node)

        def visit_ImportFrom(self, node: ast.ImportFrom):
            """
            Prevents 'from os import system' and other dangerous imports.
            """
            if node.module and node.module in self.FORBIDDEN_MODULES:
                raise PermissionError(f"Import from forbidden module '{node.module}' is not allowed.")
            self.generic_visit(node)

        def visit_Call(self, node: ast.Call):
            """
            Prevents calls to dangerous built-in functions like __import__('os').
            """
            if isinstance(node.func, ast.Name) and node.func.id in self.FORBIDDEN_BUILTINS:
                raise PermissionError(f"Call to forbidden function '{node.func.id}' is not allowed.")
            self.generic_visit(node)

        def visit_Attribute(self, node: ast.Attribute):
            """
            Prevents access to dangerous attributes like '.system'.
            This stops calls like 'x.system()' even if 'os' was imported indirectly.
            """
            if node.attr in self.FORBIDDEN_ATTRIBUTES:
                raise PermissionError(f"Access to forbidden attribute '{node.attr}' is not allowed.")
            self.generic_visit(node)

    def _exec_inline_python(self, code_string: str):
        """
        This is the fixed version of the vulnerable method. It uses the
        _SecureAstValidator to prevent malicious code from being executed.
        """
        try:
            # Step 1: Parse the untrusted code string into an AST.
            tree = ast.parse(code_string)

            # Step 2: Traverse the AST with the secure validator. This is the fix.
            # An exception is raised if any forbidden pattern is found.
            validator = self._SecureAstValidator()
            validator.visit(tree)

            # Step 3: If validation passes, execution in a sandboxed environment
            # would occur here. The vulnerability is patched because the validation
            # step will block malicious payloads before they can be executed.
            pass

        except (PermissionError, SyntaxError) as e:
            # The vulnerability is fixed because malicious code will raise an
            # exception here, preventing it from ever reaching an execution engine.
            # In a real application, this would be logged and handled.
            raise ValueError(f"Code validation failed: {e}") from e

Payload

workflow_name: exploit_rce
jobs:
  - job_name: malicious_job
    steps:
      - step_name: bypass_sandbox
        type: python_inline
        script: |
          import os
          os.system('touch /tmp/pwned_by_cve_2026_61438')

Cite this entry

@misc{vaitp:cve202661438,
  title        = {{PraisonAI RCE via crafted YAML due to insufficient AST validation.}},
  author       = {Bogaerts, Fr\'ed\'eric and Ivaki, Naghmeh and Fonseca, Jos\'e},
  year         = {2026},
  note         = {VAITP Python Vulnerability Dataset, entry CVE-2026-61438},
  howpublished = {\url{https://netpack.pt/vaitp/vulnerability/CVE-2026-61438/}}
}
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 ::