Skip to content

Reject runtime tool protocol events during session initialization #5290

Closed

Description

@petrmarinec

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.py
  • src/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.events before creating the session
  • still allows text-only initial session history
  • requires confirmation resumption to match a prior original function call and prior requested_tool_confirmations state
  • 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.py
  • python -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

  1. added
    web[Component] This issue will be transferred to adk-web
    on Apr 11, 2026
  2. added theissue type on Apr 11, 2026
  3. surajksharma07 commented on Apr 16, 2026

    @surajksharma07
    Collaborator

    @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.

  4. petrmarinec commented on Apr 16, 2026

    @petrmarinec
    ContributorAuthor

    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_confirmation event 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.

  5. surajksharma07 commented on Apr 19, 2026

    @surajksharma07
    Collaborator

    @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.

  6. added
    needs review[Status] The PR/issue is awaiting review from the maintainer
    and removed
    request clarification[Status] The maintainer need clarification or more information from the author
    on Apr 19, 2026
  7. surajksharma07 commented on Apr 19, 2026

    @surajksharma07
    Collaborator

    @wyf7107 Please have a look into it once.

  8. kappa9999 commented on Jul 29, 2026

    @kappa9999

    Independent reproduction: this remains exploitable in the exact v2.5.0 tag (1e93d82fdb5470bcb6d09caa1f3ade13058b921e), after the CVE-2026-18236 continuation-forgery fix (c03f3337).

    I used an inert registered sentinel with require_confirmation=True and the production ApiServer + TestClient path:

    1. POST /apps/test_app/users/audit-user/sessions with two imported events: an agent-authored sentinel function call and a matching agent-authored adk_request_confirmation call.
    2. Verified import alone caused no side effect (200, sentinel call count 0).
    3. Sent the approval through the ordinary POST /run new_message path as a user FunctionResponse.
    4. /run returned 200, 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.events still 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 EventActions check may be leaner than enumerating fields: current EventActions also includes route and set_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.

  9. added a commit that references this issue on Aug 14, 2026
    3fa71b6
  10. added a commit that references this issue on Aug 17, 2026
    e2b8ace
  11. added a commit that references this issue on Aug 17, 2026
    515c2c3
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Labels

needs review[Status] The PR/issue is awaiting review from the maintainerweb[Component] This issue will be transferred to adk-web

Type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions