GitHub Security Advisory: GHSA-9mqq-jqxf-grvw
Package: PraisonAI (PIP)
Severity: CRITICAL (CVSS 9.6)
CWE: CWE-20: Improper Input Validation, CWE-22: Improper Limitation of a Pathname to a Restricted Directory ('Path Traversal'), CWE-94: Improper Control of Generation of Code ('Code Injection'), CWE-829: Inclusion of Functionality from Untrusted Control Sphere, CWE-913: Improper Control of Dynamically-Managed Code Resources
Vulnerable versions: <= 4.6.33
Patched in: 4.6.34

Summary: PraisonAI MCP `tools/call` path-traversal => RCE via Python `.pth` injection

Details:
## Summary

PraisonAI's MCP (Model Context Protocol) server (`praisonai mcp serve`) registers four file-handling tools by default — `praisonai.rules.create`, `praisonai.rules.show`, `praisonai.rules.delete`, and `praisonai.workflow.show`. Each accepts a path or filename string from MCP `tools/call` arguments and joins it onto `~/.praison/rules/` (or, for `workflow.show`, accepts an absolute path) **with no containment check**. The JSON-RPC dispatcher passes `params["arguments"]` blind to each handler via `**kwargs` without validating against the advertised input schema.

By setting `rule_name="../../<some-path>"` an attacker walks out of the rules directory and writes any file the running user can write. Dropping a Python `.pth` file into the user site-packages directory escalates this primitive to **arbitrary code execution in any subsequent Python process the user spawns** — the next `praisonai` CLI invocation, an IDE script run, the user's `python` REPL, or any background Python service. The same primitive is reachable from:

- An MCP-connected LLM (Claude Desktop, Cursor, Continue.dev, Claude Code) whose context is poisoned by attacker-controlled web content / documents / emails — **no operator click required beyond ordinary "ask the LLM to summarise this page" usage**.
- `praisonai mcp serve --transport http-stream` with no `--api-key` (default), reachable from any local process / DNS-rebound browser tab / container neighbour sharing loopback.
- Stdio MCP from any prompt-injection vector that reaches the connected LLM.

No operator misconfiguration is required. No env var, flag, or config switch disables the vulnerable handlers.

---

## Details

### 1. The dispatcher accepts unvalidated kwargs

`src/praisonai/praisonai/mcp_server/server.py:281-298`:

```python
async def _handle_tools_call(self, params: Dict[str, Any]) -> Dict[str, Any]:
    """Handle tools/call request."""
    tool_name = params.get("name")
    arguments = params.get("arguments", {})

    if not tool_name:
        raise ValueError("Tool name required")

    tool = self._tool_registry.get(tool_name)
    if tool is None:
        raise ValueError(f"Tool not found: {tool_name}")

    # Execute tool
    try:
        if asyncio.iscoroutinefunction(tool.handler):
            result = await tool.handler(**arguments)        # ← no schema enforcement
        else:
            result = tool.handler(**arguments)
```

`tool.input_schema` is built reflectively from the handler signature in `registry.py:320-376` and surfaced in `tools/list` responses — but it is **never enforced** before dispatch. Whatever JSON shape the MCP client (or an LLM under prompt injection) sends becomes a `**kwargs` call.

### 2. The four registered handlers have no containment

`src/praisonai/praisonai/mcp_server/adapters/cli_tools.py`:

```python
# line 116-128 — rules.create — primary write primitive
@register_tool("praisonai.rules.create")
def rules_create(rule_name: str, content: str) -> str:
    """Create