Skip to content

fix(flows): require agent authorship before resume-dispatching a function call (#7076) - #7077

Open
dmcnelis wants to merge 1 commit into
google:mainfrom
dmcnelis:fix/resumable-author-provenance
Open

fix(flows): require agent authorship before resume-dispatching a function call (#7076)#7077
dmcnelis wants to merge 1 commit into
google:mainfrom
dmcnelis:fix/resumable-author-provenance

Conversation

@dmcnelis

@dmcnelis dmcnelis commented Sep 9, 2026

Copy link
Copy Markdown

What

Fixes the resumable tool-dispatch bypass reported in #7076.

When an app is resumable (ResumabilityConfig(is_resumable=True)), the flow replays a trailing function-call event without checking who authored it and without a model turn. Because session init accepts caller-supplied events, a client can:

  1. POST /apps/{app}/users/{u}/sessions with an event authored "user" carrying a function_call, and
  2. POST /run_sse with newMessage: null against that event's invocationId,

and the named tool executes with caller-chosen arguments — bypassing the model, the system prompt, and any model-mediated safety reasoning. Confirmed by execution on 2.7.1 and 2.8.0; the same gap is present on main at the refactored location addressed here.

Fix

Guard both event-selection paths in flows/llm_flows/_resume_utils.py by the current agent's name (require_agent_name):

  • decide_step_resume — the trailing-call replay (the path the PoC exercises).
  • decide_resume_find_target_call_event — the multi-event target search (adds an optional agent_name filter; default None preserves the pure matching behavior its unit tests cover).

A legitimate resume is unaffected: an agent authors its own paused call, so it still dispatches. A caller-forged call no longer dispatches and the flow falls through to a normal model turn.

Tests

  • Added regression tests that fail without the guard (a forged "user"-authored call is dispatched / selected) and pass with it, plus a positive test that the agent's own paused call still replays.
  • tests/unittests/flows/llm_flows/test_resume_utils.py: 35 passing.
  • tests/unittests/runners/test_resume_invocation.py: sub-agent resume still passes (the author check resolves to the current sub-agent's name).
  • Wider tests/unittests/flows/llm_flows/: 681 passing (2 unrelated collection errors from optional deps openai / google.cloud not installed locally).
  • pyink and isort clean on both files.

Scope / honest limitations

This closes the demonstrated path (a "user"-authored forged call). For full defense-in-depth, the session-init validator (_validate_session_initialization_events) could also reject caller-supplied events that carry function_call parts or are authored as anything the framework reserves, so a caller cannot forge an agent-authored pending call either. That touches the session-restore design, so I've left it as a maintainer call and kept this PR to the resume-path provenance guard. Happy to extend if you'd prefer the ingress hardening in the same change.

Note: I'll need to complete the Google CLA — the bot should indicate the exact email.

Refs: #7076

🤖 Generated with Claude Code

https://claude.ai/code/session_01KbhX6pH5CZCtymrBneXQVJ

…tion call

A resumable App dispatches a trailing function-call event with no model turn
and no check on who authored it. Session init accepts caller-supplied events,
so a client can seed a "user"-authored event carrying a function_call, start a
resumable run against its invocation_id, and have the named tool execute with
caller-chosen arguments -- bypassing the model, the system prompt, and any
model-mediated safety reasoning.

Guard both event-selection paths in _resume_utils by the current agent's name
(require_agent_name): the trailing-call replay in decide_step_resume and the
target-call search in decide_resume via _find_target_call_event. A legitimate
resume is unaffected -- the agent authored its own paused call, so it still
dispatches -- while a caller-forged call no longer does and the flow falls
through to a normal model turn.

Adds regression tests that fail without the guard (a forged "user"-authored
call is dispatched) and pass with it. Verified against 2.7.1 and 2.8.0 by
execution.

Refs: google#7076

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01KbhX6pH5CZCtymrBneXQVJ
@google-cla

google-cla Bot commented Sep 9, 2026

Copy link
Copy Markdown

Thanks for your pull request! It looks like this may be your first contribution to a Google open source project. Before we can look at your pull request, you'll need to sign a Contributor License Agreement (CLA).

View this failed invocation of the CLA check for more information.

For the most up to date status, view the checks section at the bottom of the pull request.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants