CVE-2026-47391
PraisonAI's unauthenticated A2A official example can reach real LLM-driven `eval()` tool execution
PraisonAI is a multi-agent teams system. Prior to version 4.6.40, PraisonAI's first-party A2A server example exposes an unauthenticated A2A JSON-RPC endpoint and registers a calculate(expression) tool implemented with Python eval(). The example also binds to 0.0.0.0. A remote unauthenticated attacker can send message/send to /a2a; the request reaches agent.chat(), and a real LLM can invoke the registered calculate tool. In testing with gemini/gemini-2.5-flash-lite, this resulted in arbitrary Python execution in the server process, confirmed by creation of a marker file from an unauthenticated HTTP request. The issue affects deployments following the official A2A example or similar unauthenticated public A2A deployments with unsafe tools. The default unauthenticated A2A surface also exposes task history and task cancellation APIs, increasing confidentiality and integrity impact. Version 4.6.40 patches the issue.
Attack profile
How easy it is to exploit
- Attack vector
- Network
- Complexity
- Low
- Privileges needed
- None
- User interaction
- None
What an attacker can affect
- Confidentiality
- High
- Integrity
- High
- Availability
- High
Exploit likelihood
What each source reports
CVE List Score used
9.8
PraisonAI's unauthenticated A2A official example can reach real LLM-driven `eval()` tool execution
PraisonAI is a multi-agent teams system. Prior to version 4.6.40, PraisonAI's first-party A2A server example exposes an unauthenticated A2A JSON-RPC endpoint and registers a calculate(expression) tool implemented with Python eval(). The example also binds to 0.0.0.0. A remote unauthenticated attacker can send message/send to /a2a; the request reaches agent.chat(), and a real LLM can invoke the registered calculate tool. In testing with gemini/gemini-2.5-flash-lite, this resulted in arbitrary Python execution in the server process, confirmed by creation of a marker file from an unauthenticated HTTP request. The issue affects deployments following the official A2A example or similar unauthenticated public A2A deployments with unsafe tools. The default unauthenticated A2A surface also exposes task history and task cancellation APIs, increasing confidentiality and integrity impact. Version 4.6.40 patches the issue.
- Record
CVE-2026-47391- CVSS 3.1
- 9.8 Critical
CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H - Weakness
- CWE-95, CWE-306
- Updated
- 2026-11-02
EUVDEUVD-2026-46281
9.8
PraisonAI is a multi-agent teams system. Prior to version 4.6.40, PraisonAI's first-party A2A server example exposes an unauthenticated A2A JSON-RPC endpoint and registers a calculate(expression) tool implemented with Python eval(). The example also binds to 0.0.0.0. A remote unauthenticated attacker can send message/send to /a2a; the request reaches agent.chat(), and a real LLM can invoke the registered calculate tool. In testing with gemini/gemini-2.5-flash-lite, this resulted in arbitrary Python execution in the server process, confirmed by creation of a marker file from an unauthenticated HTTP request. The issue affects deployments following the official A2A example or similar unauthenticated public A2A deployments with unsafe tools. The default unauthenticated A2A surface also exposes task history and task cancellation APIs, increasing confidentiality and integrity impact. Version 4.6.40 patches the issue.
- Record
EUVD-2026-46281- CVSS 3.1
- 9.8 Critical
CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H - Updated
- 2026-11-02
GitHub AdvisoriesGHSA-vg22-4gmj-prxw
9.8
PraisonAI's unauthenticated A2A official example can reach real LLM-driven `eval()` tool execution
Summary
The first-party PraisonAI A2A server example combines three behaviors into a remotely exploitable Critical chain:
- The example exposes an A2A server without configuring
auth_token. - The same example binds the server to
0.0.0.0. - The example registers a
calculate(expression)tool implemented with Pythoneval(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:
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:
inbox/PraisonAI/examples/python/a2a/a2a-server.py
Relevant lines:
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:
inbox/PraisonAI/src/praisonai-agents/praisonaiagents/ui/a2a/a2a.py
Relevant lines:
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:
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…
- Record
GHSA-vg22-4gmj-prxw- CVSS 3.1
- 9.8 Critical
CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H - Weakness
- CWE-95, CWE-306
- Updated
- 2026-11-02
NVD
9.8
PraisonAI is a multi-agent teams system. Prior to version 4.6.40, PraisonAI's first-party A2A server example exposes an unauthenticated A2A JSON-RPC endpoint and registers a calculate(expression) tool implemented with Python eval(). The example also binds to 0.0.0.0. A remote unauthenticated attacker can send message/send to /a2a; the request reaches agent.chat(), and a real LLM can invoke the registered calculate tool. In testing with gemini/gemini-2.5-flash-lite, this resulted in arbitrary Python execution in the server process, confirmed by creation of a marker file from an unauthenticated HTTP request. The issue affects deployments following the official A2A example or similar unauthenticated public A2A deployments with unsafe tools. The default unauthenticated A2A surface also exposes task history and task cancellation APIs, increasing confidentiality and integrity impact. Version 4.6.40 patches the issue.
- Record
CVE-2026-47391- CVSS 3.1
- 9.8 Critical
CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H - Weakness
- CWE-95, CWE-306
- Updated
- 2026-11-02
OSV
9.8
PraisonAI's unauthenticated A2A official example can reach real LLM-driven `eval()` tool execution
PraisonAI is a multi-agent teams system. Prior to version 4.6.40, PraisonAI's first-party A2A server example exposes an unauthenticated A2A JSON-RPC endpoint and registers a calculate(expression) tool implemented with Python eval(). The example also binds to 0.0.0.0. A remote unauthenticated attacker can send message/send to /a2a; the request reaches agent.chat(), and a real LLM can invoke the registered calculate tool. In testing with gemini/gemini-2.5-flash-lite, this resulted in arbitrary Python execution in the server process, confirmed by creation of a marker file from an unauthenticated HTTP request. The issue affects deployments following the official A2A example or similar unauthenticated public A2A deployments with unsafe tools. The default unauthenticated A2A surface also exposes task history and task cancellation APIs, increasing confidentiality and integrity impact. Version 4.6.40 patches the issue.
- Record
CVE-2026-47391- CVSS 3.1
- 9.8 Critical
CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H - Weakness
- CWE-306, CWE-95
- Updated
- 2026-11-02
OSVGHSA-vg22-4gmj-prxw
9.8
PraisonAI's unauthenticated A2A official example can reach real LLM-driven `eval()` tool execution
Summary
The first-party PraisonAI A2A server example combines three behaviors into a remotely exploitable Critical chain:
- The example exposes an A2A server without configuring
auth_token. - The same example binds the server to
0.0.0.0. - The example registers a
calculate(expression)tool implemented with Pythoneval(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:
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:
inbox/PraisonAI/examples/python/a2a/a2a-server.py
Relevant lines:
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:
inbox/PraisonAI/src/praisonai-agents/praisonaiagents/ui/a2a/a2a.py
Relevant lines:
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:
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…
- Record
GHSA-vg22-4gmj-prxw- CVSS 3.1
- 9.8 Critical
CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H - Weakness
- CWE-306, CWE-95
- Updated
- 2026-11-02
OSVPYSEC-2026-478
9.8
PraisonAI's unauthenticated A2A official example can reach real LLM-driven `eval()` tool execution
Summary
The first-party PraisonAI A2A server example combines three behaviors into a remotely exploitable Critical chain:
- The example exposes an A2A server without configuring
auth_token. - The same example binds the server to
0.0.0.0. - The example registers a
calculate(expression)tool implemented with Pythoneval(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:
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:
inbox/PraisonAI/examples/python/a2a/a2a-server.py
Relevant lines:
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:
inbox/PraisonAI/src/praisonai-agents/praisonaiagents/ui/a2a/a2a.py
Relevant lines:
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:
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…
- Record
PYSEC-2026-478- CVSS 3.1
- 9.8 Critical
CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H - Updated
- 2026-11-02
Affected products
Affected packages
| Package | Affected | Fixed |
|---|---|---|
praisonaiPyPI |
through 4.6.39 | 4.6.40 |
praisonaiPyPI |
before 4.6.40, plus listed versions 0.0.10.0.100.0.110.0.120.0.13 Show 45 moreShow less0.0.140.0.150.0.160.0.170.0.180.0.190.0.20.0.200.0.210.0.220.0.230.0.240.0.250.0.260.0.270.0.280.0.290.0.30.0.300.0.310.0.320.0.330.0.340.0.350.0.360.0.370.0.380.0.390.0.40.0.400.0.410.0.420.0.430.0.440.0.450.0.460.0.470.0.480.0.490.0.50.0.500.0.520.0.530.0.540.0.55 |
4.6.40 |
History
| Recorded | Changed | Sources |
|---|---|---|
| aliases | EUVD | |
| first indexed | CVE List, GitHub Advisories, NVD, OSV |
Dates are when Every CVE recorded each change. Upstream dates may differ.
In short
CVE-2026-47391 is a critical vulnerability (CVSS 9.8) in Mervinpraison Praisonai, published May 29, 2026. No source lists public exploit code. EPSS puts the chance of exploitation in the next 30 days at 1.50%, the 74th percentile. Fixed in praisonai 4.6.40.
References
github.com/MervinPraison/PraisonAI/security/advisories/GHSA-vg22-4gmj-prxw
github.com/MervinPraison/PraisonAI/pull/1793
github.com/MervinPraison/PraisonAI/commit/e0fb8e7dd1ee6759c18ed07f436c21dbd9c20747
github.com/advisories/GHSA-vg22-4gmj-prxw
github.com/CVEProject/cvelistV5/tree/main/cves/2026/47xxx/CVE-2026-47391.json
nvd.nist.gov/vuln/detail/CVE-2026-47391
github.com/MervinPraison/PraisonAI
pypi.org/project/praisonai
Left out here: 681 versions and the rest of 3 source descriptions. The JSON record has them.