CVE-2026-47391 Overview
CVE-2026-47391 is a critical remote code execution vulnerability in PraisonAI, a multi-agent teams system. The flaw exists in the first-party Agent-to-Agent (A2A) server example prior to version 4.6.40. The example exposes an unauthenticated A2A JSON-RPC endpoint and registers a calculate(expression) tool implemented with Python eval(). The server also binds to 0.0.0.0, making it reachable from any network interface. A remote unauthenticated attacker can send a message/send request to /a2a, which reaches agent.chat(), allowing a real LLM to invoke the calculate tool and execute arbitrary Python code in the server process. The issue is tracked under [CWE-95] (Improper Neutralization of Directives in Dynamically Evaluated Code).
Critical Impact
Unauthenticated remote attackers can achieve arbitrary Python code execution on the host, plus read task history and cancel tasks via the exposed A2A APIs.
Affected Products
- PraisonAI versions prior to 4.6.40
- Deployments following the official A2A server example
- Similar unauthenticated public A2A deployments registering unsafe tools
Discovery Timeline
- 2026-07-21 - CVE-2026-47391 published to NVD
- 2026-07-23 - Last updated in NVD database
Technical Details for CVE-2026-47391
Vulnerability Analysis
The vulnerability chains three unsafe defaults in the reference A2A server example. First, the JSON-RPC endpoint at /a2a accepts requests without authentication. Second, the server binds to 0.0.0.0, exposing it beyond the loopback interface. Third, the sample registers a calculate(expression) tool that passes attacker-influenced strings directly into Python eval().
When an attacker submits a message/send request, PraisonAI routes the payload to agent.chat(). The backing LLM, tested with gemini/gemini-2.5-flash-lite, parses the natural-language request and invokes the registered calculate tool. Because eval() executes arbitrary Python, the attacker achieves code execution in the server process. Researchers confirmed exploitation by creating a marker file through a single unauthenticated HTTP request.
Root Cause
The root cause is the use of Python eval() on untrusted input inside a tool that an LLM can invoke on behalf of unauthenticated remote callers. Combined with a public bind address and the absence of authentication on the A2A surface, the tool becomes a direct RCE primitive. The exposed task history and task cancellation APIs further extend confidentiality and integrity impact.
Attack Vector
An attacker sends a crafted JSON-RPC message/send request to the public /a2a endpoint. The message instructs the agent to compute an expression that embeds Python code, such as an __import__('os').system(...) payload. The LLM invokes the calculate tool, and eval() executes the payload with the privileges of the server process.
# Patched calculate() tool from examples/eval/reliability_example.py
def calculate(expression: str) -> str:
"""Calculate a math expression safely."""
import ast, operator
_OPS = {ast.Add: operator.add, ast.Sub: operator.sub, ast.Mult: operator.mul,
ast.Div: operator.truediv, ast.FloorDiv: operator.floordiv,
ast.Mod: operator.mod, ast.Pow: operator.pow,
ast.USub: operator.neg, ast.UAdd: operator.pos}
def _ev(n):
if isinstance(n, ast.Expression): return _ev(n.body)
if isinstance(n, ast.Constant) and isinstance(n.value, (int, float)): return n.value
if isinstance(n, ast.UnaryOp) and type(n.op) in _OPS: return _OPS[type(n.op)](_ev(n.operand))
if isinstance(n, ast.BinOp) and type(n.op) in _OPS: return _OPS[type(n.op)](_ev(n.left), _ev(n.right))
raise ValueError(f"Unsupported: {ast.dump(n)}")
return str(_ev(ast.parse(expression, mode="eval")))
Source: GitHub Commit e0fb8e7
Detection Methods for CVE-2026-47391
Indicators of Compromise
- Inbound HTTP POST requests to /a2a from untrusted networks containing message/send JSON-RPC payloads.
- Unexpected child processes spawned by the PraisonAI server process, such as sh, bash, curl, or python -c.
- Creation of unexpected files by the PraisonAI process (marker files, dropped scripts, or web shells).
- Outbound network connections from the PraisonAI host to unfamiliar IP addresses following A2A requests.
Detection Strategies
- Inspect A2A request bodies for expressions containing __import__, os.system, subprocess, open(, or backtick-style shell metacharacters passed to calculate.
- Baseline the PraisonAI process tree and alert on any deviation, since a math tool should never spawn shells or interpreters.
- Correlate /a2a traffic with subsequent filesystem writes or network egress from the same process.
Monitoring Recommendations
- Enable process, file, and network telemetry on hosts running PraisonAI A2A servers.
- Log full JSON-RPC request bodies at a reverse proxy for forensic review.
- Alert on any A2A endpoint reachable on 0.0.0.0 without an authenticating proxy in front of it.
How to Mitigate CVE-2026-47391
Immediate Actions Required
- Upgrade PraisonAI to version 4.6.40 or later, which replaces eval() with a safe AST-based evaluator.
- Remove or disable any custom tools that call eval(), exec(), or subprocess on user-supplied input.
- Restrict the A2A server bind address to 127.0.0.1 or an internal interface until authentication is enforced.
- Place authenticated reverse proxies or mTLS in front of any A2A endpoint exposed beyond localhost.
Patch Information
Version 4.6.40 patches the issue. The fix replaces eval() in examples/eval/reliability_example.py and examples/mcp/multi_server.py with an AST-based safe evaluator that only permits arithmetic operators. Details are available in GitHub Pull Request #1793 and GitHub Security Advisory GHSA-vg22-4gmj-prxw.
Workarounds
- Bind the A2A server to 127.0.0.1 and require a front-end proxy that enforces authentication.
- Remove the calculate tool registration or replace it with the AST-based implementation from the upstream patch.
- Disable exposed task history and task cancellation APIs until access controls are in place.
- Run PraisonAI in a hardened container with a read-only filesystem and no outbound network access to limit blast radius.
# Configuration example: restrict A2A server bind and upgrade
pip install --upgrade "praisonai>=4.6.40"
# Bind only to loopback and place behind an authenticating reverse proxy
export A2A_HOST=127.0.0.1
export A2A_PORT=8000
# Verify the endpoint is not reachable from external interfaces
curl -s -o /dev/null -w "%{http_code}\n" http://<public-ip>:8000/a2a || echo "unreachable (expected)"
Disclaimer: This content was generated using AI. While we strive for accuracy, please verify critical information with official sources.

