PraisonAI has unauthenticated RCE via `tool_override.py` (CVE-2026-40287 patch bypass)
Date: 2026-05
Affected: pip/praisonai
Attack vector: rce
Category: vulnerability-disclosure
Severity: Critical
CVE: CVE-2026-40287
CVE-2026-40288
CVE-2026-40289
CVE-2026-44334
CWE: CWE-306
CWE-426
CWE-78
CWE-94
CVSS: 8.4 (CVSS:3.1/AV:L/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H)

## TL;DR CVE-2026-40287's fix gated `tools.py` auto-import behind `PRAISONAI_ALLOW_LOCAL_TOOLS=true` in **two** files (`tool_resolver.py`, `api/call.py`). A **third** import sink in `praisonai/templates/tool_override.py` was missed and remains unguarded. It is reached by the recipe runner on every recipe execution and is **remotely** triggerable through `POST /v1/recipes/run` with a `recipe` value pointing at any local absolute path *or* any GitHub repo (because `SecurityConfig.allow_any_github` defaults to `True`). The attacker drops a `tools.py` next to `TEMPLATE.yaml`; the server `exec_module()`s it. No auth required by default, no environment opt-in required. ## Patch coverage gap CVE-2026-40287 was fixed in v4.5.139 by adding an env-var gate at: | File | Line | Gate | |---|---|---| | `praisonai/tool_resolver.py` | 77 | `if os.environ.get("PRAISONAI_ALLOW_LOCAL_TOOLS", "").lower() != "true":` | | `praisonai/api/call.py` | 80 | same | But the equivalent sinks in `praisonai/templates/tool_override.py` were **not** patched: ```python # tool_override.py - create_tool_registry_with_overrides() 332 cwd_tools_py = Path.cwd() / "tools.py" 333 if cwd_tools_py.exists(): 334 try: 335 tools = loader.load_from_file(str(cwd_tools_py)) # <-- exec_module 336 registry.update(tools) 337 except Exception: 338 pass 339 341 # 4. Template-local tools.py 342 if template_dir: 343 tools_py = Path(template_dir) / "tools.py"


Mitigations:

Architectural layers (MAESTRO):