GitHub Advisoryadvisory · PyPI
GHSA-vg22-4gmj-prxw PraisonAI's unauthenticated A2A official example can reach real LLM-driven `eval()` tool execution
CRITICAL
Official page ↗
- Aliases
- CVE-2026-47391
- Ecosystem
- PyPI
- Packages
- PraisonAI
- Severity
- Critical
- CVSS vector
- CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H
- Weaknesses
- CWE-306, CWE-95
- Reviewed
- 2026-05-29
Affected packages
• PyPI PraisonAI: < 4.6.40 → fixed in 4.6.40
Description
Summary
The first-party PraisonAI A2A server example combines three behaviors into a remotely exploitable Critical chain:
1. The example exposes an A2A server without configuring `auth_token`.
2. The same example binds the server to `0.0.0.0`.
3. The example registers a `calculate(expression)` tool implemented with Python `eval(expression)`.
An unauthenticated network client can send a JSON-RPC `message/send` request to `/a2a`. The A2A handler passes the attacker-controlled message to `agent.chat()`. With a real Gemini LLM (`gemini/gemini-2.5-flash-lite`), the model invoked the registered `calculate` tool, causing the example's `eval()` call to execute Python in the server process. The canary wrote a marker file from an unauthenticated `/a2a` request.
This is not a claim that every A2A deployment is automatically RCE. The Critical chain is confirmed for the first-party A2A example, and for deployments that follow the same pattern: public unauthenticated A2A plus an unsafe tool such as this `eval()`-based `calculate` tool. The default unauthenticated A2A surface is the remote entry point; the official example's `eval()` tool provides the code execution sink.
Earlier note:
The unsafe official example existed earlier, but the complete unauthenticated `/a2a` `message/send` to `agent.chat()` exploit chain is only claimed here for versions where that endpoint is present and confirmed.
Trust Boundary
The boundary that should be preserved is:
```text
Unauthenticated network clients must not be able to drive server-side agent tools that can execute code or mutate server state.
```
The affected example breaks that boundary. A remote unauthenticated A2A client can supply a prompt that reaches the server's LLM-backed agent. The LLM can then invoke a registered local tool. In the official example, that registered local tool directly evaluates attacker-influenced input with `eval()`.
Vulnerable Code
Official example:
```text
inbox/PraisonAI/examples/python/a2a/a2a-server.py
```
Relevant lines:
```python
23 def calculate(expression: str) -> str:
24 """Calculate a mathematical expression."""
25 try:
26 return f"Result: {eval(expression)}"
27 except Exception:
28 return "Invalid expression"
30 agent = Agent(
31 name="Research Assistant",
32 role="Research Analyst",
33 goal="Help users research topics and answer questions",
34 tools=[search_web, calculate]
35 )
38 a2a = A2A(
39 agent=agent,
40 url="http://localhost:8000/a2a",
41 version="1.0.0"
42 )
51 if __name__ == "__main__":
52 import uvicorn
53 uvicorn.run(app, host="0.0.0.0", port=8000)
```
A2A defaults and authentication behavior:
```text
inbox/PraisonAI/src/praisonai-agents/praisonaiagents/ui/a2a/a2a.py
```
Relevant lines:
```python
125 def serve(self, host: str = "0.0.0.0", port: int = 8000):
...
142 uvicorn.run(app, host=host, port=port)
162 # Auth dependency — only applied to POST /a2a, not discovery endpoints
163 async def _verify_auth(authorization: Optional[str] = Header(None)):
164 """Verify bearer token if auth_token is configured."""
165 if self.auth_token is None:
166 return # No auth configured — open access
192 from fastapi import Depends
193 _a2a_deps = [Depends(_verify_auth)] if self.auth_token else []
194 @router.post("/a2a", dependencies=_a2a_deps)
195 async def handle_jsonrpc(request: Request):
```
`message/send` reaches the agent:
```python
309 try:
310 # Extract user input text
311 user_input = extract_user_input([message])
312
313 # Run agent or agents (offload sync call to thread pool)
314 if self.agent:
315 response = await asyncio.to_thread(self.agent.chat, user_input)
```
Attack Model
The attacker is an unauthenticated remote client that can reach the A2A HTTP service. This is realistic because the official example binds to `0.0.0.0`, does not configure `auth_token`, and exposes `/a2a`.
The attacker does not need:
• repository write access
• local shell access
• a valid bearer token
• a compromised maintainer account
• access to server secrets
The attacker only sends a JSON-RPC request to `/a2a`.
Non-Claims
This report does not claim:
• all A2A deployments are automatically RCE
• `auth_token`-protected A2A deployments are affected in the same way
• safe, read-only tools provide the same impact as the official example's `eval()` sink
• deterministic tool invocation is required in all attacks
The real LLM canary demonstrates that a normal model-backed agent can invoke the official example's unsafe tool from an unauthenticated `/a2a` request. The deterministic control proof is included only to isolate the server-to-tool sink behavior.
Impact
For the official example and similar deployments:
• remote prompt-to-tool execution from an unauthenticated network request
• arbitrary Python execution through the example `calculate()` tool's `eval()`
• compromise of the server process privileges
• potential read/write access to application files reachable by that process
• potential credential or environment variable exposure if a payload reads process state
• denial of service or data corruption through executed code
Supporting evidence also confirmed that default unauthenticated A2A exposes task state APIs (`tasks/list`, `tasks/get`, `tasks/cancel`) and stores text plus structured `DataPart` payloads in task history. That is a separate confidentiality/integrity problem and strengthens the risk of leaving A2A unauthenticated.
Reproduction Environment
Tested repository state:
```text
commit: 4985415e
describe: v4.6.37-13-g4985415e
```
Real LLM used:
```text
gemini/gemini-2.5-flash-lite
```
The API key value was not printed. The PoC only prints whether a provider credential is present.
The PoC uses FastAPI `TestClient` to exercise the same HTTP route and request handling stack without opening a public listening socket during testing. The official example's `__main__` path binds to `0.0.0.0` when run as a server.
Reproduction Steps
From the repository root:
```bash
cd
python3 -m venv .venv-real-llm
source .venv-real-llm/bin/activate
python -m pip install -U pip
python -m pip install litellm fastapi "pydantic>=2" httpx uvicorn
```
Set a Gemini API key without writing it to shell history:
```bash
unset GEMINI_API_KEY
read -rsp "GEMINI_API_KEY: " GEMINI_API_KEY
echo
export GEMINI_API_KEY
```
Run the real LLM canary:
```bash
REAL_LLM_MODEL="gemini/gemini-2.5-flash-lite" \
REAL_LLM_TOOL_CHOICE=auto \
python out/prove-official-a2a-example-real-llm-canary.py \
| tee out/official-a2a-example-real-llm-canary-gemini-25-flash-lite-proof.log
```
Expected success marker:
```text
OFFICIAL_A2A_EXAMPLE_REAL_LLM_UNAUTH_HTTP_TO_CUSTOM_EVAL_TOOL_CONFIRMED
```
Expected marker file:
```text
out/official-a2a-example-real-llm-canary.txt
```
Expected marker content:
```text
OFFICIAL_A2A_EXAMPLE_REAL_LLM_CONFIRMED
```
Real LLM Proof Log
The following is the successful proof log from `out/official-a2a-example-real-llm-canary-gemini-25-flash-lite-proof.log`:
```text
=== Real LLM environment ===
model: gemini/gemini-2.5-flash-lite
provider credential present: True
tool_choice mode: auto
API key values printed: False
=== Official A2A example preconditions ===
example path: /home/employees/fox-linux-qa-002/work/inbox/PraisonAI/examples/python/a2a/a2a-server.py
A2A auth_token configured: None
A2A.serve default host: '0.0.0.0'
example __main__ binds host 0.0.0.0: True
registered tool names: ['search_web', 'calculate']
calculate source contains eval: True
calculate in DEFAULT_DANGEROUS_TOOLS: False
=== Real LLM HTTP-to-eval-tool result ===
HTTP status without Authorization: 200
marker exists: True
marker path: /home/employees/fox-linux-qa-002/work/out/official-a2a-example-real-llm-canary.txt
marker content matches canary: True
response contains marker path: True
response contains eval tool prefix: False
response body preview: {"id": "official-a2…
Related
Alias · CVE (1)
- CVE-2026-47391 PraisonAI is a multi-agent teams system. vulnerability