fix: enforce denied tool confirmations centrally - #7151
AnvitDevadiga wants to merge 5 commits into
Conversation
|
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. |
71c0be8 to
1c3bd19
Compare
1c3bd19 to
3557a41
Compare
|
Good fix — moving denial enforcement to the framework boundary instead of trusting each One adjacent question worth asking before merge, not asserted as a bug since I haven't reproduced it against this exact tree: does the resume path guard against the same Reason I ask: we hit the identical shape in our own verdict-issuing service — a pure function computing a decision reference had no state, so an identical still-valid verdict could be replayed to authorize a second dispatch (caught via a negative-control test explicitly checking for replay, not by inspection). The fix there was a small addition on top of the same "enforce at the boundary" pattern this PR already uses: a primary-keyed consumed-set with atomic check-and-mark, so a race between two identical resume calls has exactly one winner instead of two executions. If |
|
Following @surajksharma07's request in #7148, I validated the proposed shared-caller workaround locally on google-adk==2.9.1 using the original deterministic fake-LLM/custom-BaseTool harness. The only effect is a temporary marker file; no external model or service is contacted. Results:
All four assertions passed. This validates the suggested caller-level workaround for the reported custom-tool path. One distinction matters before treating this as validation of this PR: the current head 3557a41 filters denied calls in tools/_confirmation.py, whereas the workaround I tested guards _call_tool_async (in _tool_caller.py in release 2.9.1). I have not run this PR head, so this comment is not an end-to-end verification of #7151. Please retain a runner-level regression with a custom BaseTool that does not inspect the verdict, plus the approved control. The replay question above is also outside the scope of my four-case check. |
|
Thanks for pointing this out. You’re right that the existing event-history deduplication does not provide an atomic check-and-mark guarantee for concurrent resume requests. I updated the implementation so each confirmation is claimed atomically by the invocation context before dispatch. A repeated or concurrent submission for the same I also added the protection for denied confirmations, since those should be consumed exactly once as well. Syntax and diff validation pass; the targeted test suite is currently blocked in this environment because PyPI access is unavailable. |
|
Verified this against the actual PR head (6627ed4), not just the comment thread — the fix is the right shape: I went one step further before calling it closed: does this survive a crash/resume into a new process? What the PR doesn't have yet: a direct regression test for the concurrency race itself. The diff's two test changes just update expected deny-path messages — nothing exercises two simultaneous calls into |
What changed
Enforce ToolConfirmation decisions at the ADK framework boundary.
Why
Custom BaseTool implementations can currently execute after a user clicks Decline unless each tool reimplements the denial check. This fixes the issue described in #7148 and makes the documented confirmation flow default-deny.
Testing
Closes #7148