GHSA-vg22-4gmj-prxw: PraisonAI's unauthenticated A2A official example can reach real LLM-driven `eval()` tool execution
SummaryThe 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 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 BoundaryThe boundary that should be preserved is: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 CodeOfficial example:Relevant lines:A2A defaults and authentication behavior:Relevant lines:`message/send` reaches the agent:Attack ModelThe 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 accesslocal shell accessa valid bearer tokena compromised maintainer accountaccess to server secretsThe attacker only sends a JSON-RPC request to `/a2a`.Non-ClaimsThis report does not claim:all A2A deployments are automatically RCE`auth_token`-protected A2A deployments are affected in the same waysafe, read-only tools provide the same impact as the official example's `eval()` sinkdeterministic tool invocation is required in all attacksThe 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.ImpactFor the official example and similar deployments:remote prompt-to-tool execution from an unauthenticated network requestarbitrary Python execution through the example `calculate()` tool's `eval()`compromise of the server process privilegespotential read/write access to application files reachable by that processpotential credential or environment variable exposure if a payload reads process statedenial of service or data corruption through executed codeSupporting 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 EnvironmentTested repository state:Real LLM used: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 StepsFrom the repository root:Set a Gemini API key without writing it to shell history:Run the real LLM canary:Expected success marker:Expected marker file:Expected marker content:Real LLM Proof LogThe following is the successful proof log from `out/official-a2a-example-real-llm-canary-gemini-25-flash-lite-proof.log`:Note: `response contains eval tool prefix: False` is not a failure. The model summarized the tool result instead of returning the raw `Result: ...` string, but the marker file was created and its content matched the canary.Proof-of-Concept CodeThe following PoC is self-contained. It loads the first-party A2A example, replaces the default LLM with a real LiteLLM-backed Gemini model, sends an unauthenticated JSON-RPC request to `/a2a`, and checks whether the example's `eval()` tool wrote the marker file.Additional Control ProofA deterministic control proof also confirmed that once a tool call reaches the official example's `calculate` tool, the `eval()` sink executes arbitrary Python:This control proof is not the primary evidence because it uses a deterministic fake LLM. The primary evidence above uses a real Gemini LLM and should be preferred.Additional A2A Boundary EvidenceDefault A2A with `auth_token=None` exposes task APIs without authentication:This demonstrates that configuring `auth_token` changes the boundary materially. Without it, `/a2a` is open to unauthenticated clients.Why This Is Not Just MisconfigurationThe issue is not simply that an application author deliberately wrote a dangerous private tool. The vulnerable chain is present in first-party material:the official example is an A2A server example intended to be run by usersit registers an `eval()`-based toolit does not configure an auth tokenit binds to `0.0.0.0`the framework allows `auth_token=None` to remove authentication from `/a2a`the JSON-RPC `message/send` path reaches `agent.chat()` and registered toolsUsers following this example can expose a remotely reachable, unauthenticated prompt-to-code-execution service.Recommended FixesShort-term:Remove `eval()` from the official A2A example. Use a safe expression parser or a fixed arithmetic parser instead.Do not publish examples that combine public bind, no authentication, and code-capable tools.Change the example to bind to `127.0.0.1` by default.Require an explicit `auth_token` or other authentication mechanism before allowing `0.0.0.0` binding.Add a startup failure for `host="0.0.0.0"` when `auth_token` is absent.Framework-level hardening:Make `A2A.serve()` default to `127.0.0.1`.Require authentication for `/a2a` by default.Add an explicit unsafe flag for unauthenticated public A2A, for example `allow_unauthenticated_public=True`.Treat custom tools capable of code execution as dangerous even when the function name is not in `DEFAULT_DANGEROUS_TOOLS`.Add documentation warnings that public A2A servers must not expose tools that execute code, shell commands, file writes, or network access without authorization and review.Regression tests:Test that `A2A(agent=..., auth_token=None).serve(host="0.0.0.0")` fails or warns loudly.Test that official examples do not contain `eval()`, `exec()`, shell execution, or file mutation tools on unauthenticated public endpoints.Test that `/a2a` returns `401` when authentication is required.Suggested Advisory DescriptionPraisonAI'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.
CVSS v3.1: 9.8