Summary
Dangerous FunctionTools (e.g. the shipped ExecuteBashTool) are gated by a human-in-the-loop confirmation, but the approval is only validated to come from an event whose author == "user". Inbound A2A messages are converted to role='user' turns, so a remote A2A peer can supply the {confirmed: true} approval and self-approve a pending dangerous tool call - a confused deputy across the A2A trust boundary. The confirmation is meant to require the human operator; a peer is not the operator.
Affected
google-adk (confirmed 2.5.0; same logic on main):
src/google/adk/flows/llm_flows/request_confirmation.py - the only trust check is event.author == "user"; the approval {confirmed: true} is parsed verbatim.
src/google/adk/tools/bash_tool.py - gate is tool_confirmation.confirmed, then create_subprocess_exec.
src/google/adk/a2a/converters/request_converter.py / part_converter.py - inbound A2A message -> Content(role='user'); a function_response DataPart -> genai function_response part.
Reproduce
Agent configured with ExecuteBashTool. Over A2A, a peer sends a function_response DataPart for the pending adk_request_confirmation id with {confirmed: true} -> the tool executes. Negative control {confirmed: false} -> rejected. (Also reachable by any caller of /run / /run_sse, which have no auth by default.)
Impact
Defeats the only safety control on ExecuteBashTool (and any require-confirmation tool) for A2A peers and unauthenticated API callers. CWE-306 / confused deputy.
Fix
Refuse adk_request_confirmation approvals on A2A-originated invocations (a peer is not the operator). The /run vector additionally requires authenticating the ADK API server in production - the HITL confirmation is not a substitute for network auth. A PR follows.
Reported to Google (b/538096101); the team asked that it be disclosed publicly here.
Summary
Dangerous
FunctionTools (e.g. the shippedExecuteBashTool) are gated by a human-in-the-loop confirmation, but the approval is only validated to come from an event whoseauthor == "user". Inbound A2A messages are converted torole='user'turns, so a remote A2A peer can supply the{confirmed: true}approval and self-approve a pending dangerous tool call - a confused deputy across the A2A trust boundary. The confirmation is meant to require the human operator; a peer is not the operator.Affected
google-adk(confirmed 2.5.0; same logic onmain):src/google/adk/flows/llm_flows/request_confirmation.py- the only trust check isevent.author == "user"; the approval{confirmed: true}is parsed verbatim.src/google/adk/tools/bash_tool.py- gate istool_confirmation.confirmed, thencreate_subprocess_exec.src/google/adk/a2a/converters/request_converter.py/part_converter.py- inbound A2A message ->Content(role='user'); afunction_responseDataPart -> genaifunction_responsepart.Reproduce
Agent configured with
ExecuteBashTool. Over A2A, a peer sends afunction_responseDataPart for the pendingadk_request_confirmationid with{confirmed: true}-> the tool executes. Negative control{confirmed: false}-> rejected. (Also reachable by any caller of/run//run_sse, which have no auth by default.)Impact
Defeats the only safety control on
ExecuteBashTool(and any require-confirmation tool) for A2A peers and unauthenticated API callers. CWE-306 / confused deputy.Fix
Refuse
adk_request_confirmationapprovals on A2A-originated invocations (a peer is not the operator). The/runvector additionally requires authenticating the ADK API server in production - the HITL confirmation is not a substitute for network auth. A PR follows.Reported to Google (b/538096101); the team asked that it be disclosed publicly here.