Repository navigation
Reject runtime tool protocol events during session initialization #5290
Description
Summary
The session creation API accepts an optional events list and appends those events directly to the new session. These events can currently include internal ADK runtime/tool protocol fields such as function_call, function_response, long_running_tool_ids, and actions.requested_tool_confirmations.
This allows client-supplied session initialization data to be treated later as ADK-generated tool state. In particular, the HITL confirmation resolver can consume a seeded adk_request_confirmation function call with an embedded originalFunctionCall, then resume that embedded registered tool call when a matching confirmation response is sent.
Affected code
src/google/adk/cli/adk_web_server.pysrc/google/adk/flows/llm_flows/request_confirmation.py
Problem
Session initialization is useful for restoring conversation history, but runtime-only tool protocol fields should not cross the client/API trust boundary as authoritative ADK-generated state.
A client-provided event can currently seed data that looks like an internal confirmation request. The confirmation resolver only needs the confirmation function-call id and originalFunctionCall payload to map the later confirmation response back to a tool call. That can cause a registered tool to be invoked without a real prior model function call and without a real prior ADK-generated confirmation request.
Expected behavior
Client-supplied session initialization events should be limited to non-runtime conversation history. Function calls, function responses, long-running tool ids, and internal EventActions runtime fields should be rejected at the HTTP session initialization boundary.
The confirmation resolver should also only resume tool calls that are backed by real prior function-call and confirmation-request state in the current event history.
Proposed fix
I have a PR prepared that:
- rejects runtime/tool protocol fields in
CreateSessionRequest.eventsbefore creating the session - still allows text-only initial session history
- requires confirmation resumption to match a prior original function call and prior
requested_tool_confirmationsstate - keeps existing HITL confirmation behavior working
Validation
The PR includes regression tests covering:
- text-only session initialization is still accepted
- session initialization rejects function-call/function-response runtime events
- session initialization rejects internal runtime action metadata
- forged confirmation requests do not resume tools
- existing HITL confirmation runner flows still pass
Local validation:
python -m pyink --check src/google/adk/flows/llm_flows/request_confirmation.py src/google/adk/cli/adk_web_server.py tests/unittests/flows/llm_flows/test_request_confirmation.py tests/unittests/cli/test_fast_api.pypython -m pytest tests/unittests/flows/llm_flows/test_request_confirmation.py tests/unittests/cli/test_fast_api.py::test_create_session_accepts_initial_text_events tests/unittests/cli/test_fast_api.py::test_create_session_rejects_runtime_tool_events tests/unittests/cli/test_fast_api.py::test_create_session_rejects_runtime_action_events tests/unittests/runners/test_run_tool_confirmation.py -q
Result: 17 passed.
I also ran tests/unittests/cli/test_fast_api.py; on Windows it has unrelated baseline failures that I reproduced on clean origin/main: two CRLF assertion failures in builder GET tests and two A2A temporary-directory teardown errors. Excluding those baseline Windows-only cases, the FastAPI file passes locally: 61 passed, 4 deselected.
Activity
surajksharma07 commentedon Apr 16, 2026
@petrmarinec the create_session handler in adk_web_server.py appends client-supplied events with no validation, and _resolve_confirmation_targets in request_confirmation.py will resolve any adk_request_confirmation FC it finds in session history regardless of whether ADK generated it, which together let a crafted session seed invoke a registered tool without a real prior model call.
The two-part approach in PR #5291 looks right — rejecting runtime protocol fields at the HTTP boundary and requiring the original function call to exist in real session history before the resolver acts on it.
The one thing worth verifying carefully is the backward-compat path: text-only events in CreateSessionRequest.events should still be accepted after the validation, and existing HITL confirmation flows with a genuine prior model FC should continue to resolve correctly.
If you can confirm both paths hold up against your test setup, the PR looks ready for a review pass.
petrmarinec commentedon Apr 16, 2026
Hi, I confirmed both compatibility paths locally.
For
CreateSessionRequest.events, text-only initialization events are still accepted after the validation. The runtime protocol checks reject function calls, function responses, long-running tool IDs, and runtime action fields, but they do not reject plain text history.For HITL confirmation, the existing flow with a genuine prior model function call still resolves correctly. The resolver only proceeds when the
adk_request_confirmationevent points back to an original function call that is actually present in prior session history and the corresponding requested tool confirmation exists.The targeted create-session and request-confirmation tests pass locally.
surajksharma07 commentedon Apr 19, 2026
@petrmarinec both confirmations are exactly what was needed!
The text-only acceptance tells us the HTTP boundary validation is correctly scoped to runtime protocol fields only (function calls, function responses, long_running_tool_ids, runtime action metadata) and doesn't touch plain conversation content — which is the right place to draw that line.
The HITL compat result is equally important: the dual-state check in the resolver (original FC present in prior history AND corresponding requested_tool_confirmation also present) doesn't over-reject legitimate flows, which was the main risk with tightening that path.
Both trust boundaries are sitting in the right place — input validation at the web server, state-existence check inside the resolver.
Team is looking more into it.
surajksharma07 commentedon Apr 19, 2026
@wyf7107 Please have a look into it once.
kappa9999 commentedon Jul 29, 2026
Independent reproduction: this remains exploitable in the exact
v2.5.0tag (1e93d82fdb5470bcb6d09caa1f3ade13058b921e), after the CVE-2026-18236 continuation-forgery fix (c03f3337).I used an inert registered sentinel with
require_confirmation=Trueand the productionApiServer+TestClientpath:POST /apps/test_app/users/audit-user/sessionswith two imported events: an agent-authored sentinel function call and a matching agent-authoredadk_request_confirmationcall.200, sentinel call count0).POST /runnew_messagepath as a userFunctionResponse./runreturned200, the sentinel executed once, and the response contained the forged original call id.No duplicate IDs, forged user event, dynamic-confirmation metadata, external model, network tool, or real side effect was involved. The shipped processor verifies that imported history is self-consistent, but
CreateSessionRequest.eventsstill lets the caller forge the provenance that those checks trust.PR #5291 already targets the correct primary boundary. For a current-main rebase, a future-proof non-default
EventActionscheck may be leaner than enumerating fields: currentEventActionsalso includesrouteandset_model_response, which are not covered by the older helper. Text-only history can remain accepted. I can provide the complete inert regression or help validate a rebased minimal patch if useful.fix: validate session initialization events
fix: validate session initialization events
fix: validate session initialization events