Skip to content

[BUG] evo-ai A2A route accepts a different agent API key and exposes task/push/history control #52

Description

@N0zoM1z0

Summary

The A2A HTTP route validates that the supplied API key exists globally, then loads the target agent_id independently. In the replay, an API key belonging to agent A is rejected on agent B's normal chat route, but the same key is accepted on agent B's A2A route. The accepted caller can set/get push configuration, cancel a task by taskId, and reuse a context key to receive agent B history.

Affected Target Verified

  • Repository: https://github.com/evolution-foundation/evo-ai
  • Component: FastAPI A2A routes in src/api/a2a_routes.py
  • Affected commit verified: 7ff7a1455c68c9da6aab7f1d16617570374f81b8
  • Vulnerability type: Authorization bypass / cross-agent task and context authority

Preconditions

  • The deployment exposes /api/v1/a2a/{agent_id}.
  • Agents use per-agent API keys as a security boundary.
  • A caller knows or guesses a target task id or context/message id, or obtains it through normal task flow.

Steps To Reproduce

From the attachment directory:

bash reproduce.sh

The script clones the target repository, checks out the verified commit, prepares a local test environment, and runs the bundled PoC. It does not contact attacker-controlled services; the setup only downloads the public repository and normal build dependencies.

To run against an existing checkout:

EVO_AI_TARGET_DIR=/path/to/checkout bash reproduce.sh

The wrapper accepts EVO_AI_TARGET_DIR for an existing checkout. It also accepts the target commit override documented in attachments/reproduce.sh.

Technical Details

a2a_routes.py calls the global API-key verification path before loading the target route agent. The normal chat route separately checks that the API key matches the specific target agent config. The A2A route then stores push config by bare taskId and promotes inbound messageId into downstream conversation context.

Expected Behavior

An API key accepted on /api/v1/a2a/{agent_id} should be authorized for that
specific target agent before any A2A method is dispatched. Task, push-config,
and context-continuation state should remain scoped to the authenticated agent
or principal.

Actual Behavior

  • Invalid A2A key is rejected with 401.
  • Agent A key is rejected on agent B normal chat route.
  • Agent A key is accepted on agent B A2A route for push config set/get and cancel.
  • Same-context message/send returns Alice history; fresh-context negative control does not.

Impact

A caller with one valid agent key can operate on another agent's A2A control
plane when the A2A route only validates that the key exists globally. The
negative control is important: the same agent A key is rejected on agent B's
normal chat route, so the A2A route is weaker than the product's existing
per-agent API-key boundary.

If agents represent different users, workspaces, integrations, or data
contexts, this lets a caller cross from their own agent into another agent's
A2A task surface. The reproduced operations include setting and reading push
configuration, canceling task state, and reusing a context/message key to
receive prior history from the target agent.

Push configuration control is especially sensitive because it can redirect
future task updates or artifacts to a callback chosen by the wrong key holder.
Context history reuse can expose prior prompts and responses from the target
agent. Cancellation lets the wrong key holder interfere with another agent's
task lifecycle.

Suggested Fix

Bind the presented API key to the target agent_id before dispatching A2A JSON-RPC methods. Store task and push-config state under the authenticated agent/principal as well as task id. Treat context/message ids as correlation handles, not authorization handles.

Attachments

attachments.zip

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions