Skip to content

fix(tools): keep auth credentials out of the caller's tool args - #7163

Open
1aifanatic wants to merge 3 commits into
google:mainfrom
1aifanatic:fix/rest-api-tool-credential-in-tool-args
Open

1aifanatic wants to merge 3 commits into
google:mainfrom
1aifanatic:fix/rest-api-tool-credential-in-tool-args

Conversation

@1aifanatic

Copy link
Copy Markdown

Link to Issue or Description of Change

1. Link to an existing issue (if applicable):

Problem:

RestApiTool.call() merged the tool's auth parameters (API key, or Bearer <token>) into the args dict it received, in place. The tool pipeline hands that same function_args object to every plugin/agent after_tool_callback and to trace_tool_call(). trace_tool_call() runs after the tool returns and records it as the default-on span attribute gcp.vertex.agent.tool_call_args. So the credential leaked into traces and into whatever after-tool plugins record, for every OpenAPIToolset, APIHubToolset, GoogleApiToolset, and ApplicationIntegrationToolset tool.

IntegrationConnectorTool.run_async() did the same with the end user's OAuth access token (dynamic_auth_config), and also logged the resulting dict at INFO.

Solution:

Work on a copy of args in both places, as AuthenticatedFunctionTool already does (args_to_call = args.copy()), and log the connector's args before the token is added. A shallow copy is enough because only top-level keys are added. This also means a 401 retry (self.call(args=args, ...)) starts from the caller's clean args instead of args that already carry the stale credential.

Testing Plan

Unit Tests:

  • I have added or updated unit tests for my change.
  • All unit tests pass locally.

New regression tests (both fail on main, pass with this change):

  • test_rest_api_tool.py::TestRestApiTool::test_call_does_not_add_auth_params_to_caller_args: the caller's dict is unchanged after call(), and the API key still reaches the request header.
  • test_integration_connector_tool.py::test_run_with_auth_async_keeps_token_out_of_caller_args_and_logs: the caller's dict is unchanged, dynamic_auth_config still reaches RestApiTool.call, and the token is not in the INFO log.
$ pytest tests/unittests/tools/openapi_tool tests/unittests/tools/application_integration_tool \
    tests/unittests/tools/apihub_tool tests/unittests/tools/google_api_tool tests/unittests/auth tests/unittests/flows
1597 passed

$ pytest tests/unittests -n 8   # Python 3.12, Windows 11
43 failed, 15069 passed, 102 skipped, 27 xfailed, 2 xpassed

None of the 43 failures are in tools/. All of them also fail on an unmodified origin/main checkout on the same machine: 40 deterministically (Windows-specific tests in cli/ deploy, conformance, scripts/check_new_py_files, path normalization, the unsafe local code executor, the import allowlist, and yaml), and 3 skill-telemetry cases in telemetry/test_functional.py that fail intermittently (1 to 2 per run) on clean main in this environment. I ran on Python 3.12 only, not the full tox matrix; the change only uses dict.copy().

Manual End-to-End (E2E) Tests:

A real Runner + LlmAgent + OpenAPIToolset with API-key auth, a mocked model that calls the tool, a stubbed HTTP call, an after_tool_callback, and an in-memory OTel span exporter. The script is in the linked issue.

Before (main):

after_tool_callback args : {'order_id': 'A-42', '_auth_prefix_vaf_X-API-Key': 'sk-live-SECRET-API-KEY-123'}
execute_tool span args   : ['{"order_id": "A-42", "_auth_prefix_vaf_X-API-Key": "sk-live-SECRET-API-KEY-123"}']
secret in callback args  : True
secret in span attribute : True

After (this branch). The key is still sent in the X-API-Key request header:

after_tool_callback args : {'order_id': 'A-42'}
execute_tool span args   : ['{"order_id": "A-42"}']
secret in callback args  : False
secret in span attribute : False

Checklist

  • I have read the CONTRIBUTING.md document.
  • I have performed a self-review of my own code.
  • I have commented my code, particularly in hard-to-understand areas.
  • I have added tests that prove my fix is effective or that my feature works.
  • New and existing unit tests pass locally with my changes.
  • I have manually tested my changes end-to-end.
  • Any dependent changes have been merged and published in downstream modules.

Additional context

Session events are unaffected: the persisted FunctionCall args are deep-copied before the tool runs, so this only affected callbacks, traces, and logs.

🤖 Generated with Claude Code

https://claude.ai/code/session_013vXxD1ga1hnCq2uFRwNks7

@google-cla

google-cla Bot commented Sep 18, 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.

RestApiTool.call() merged the tool's auth parameters (an API key or a
bearer token) into the args dict it was given. The tool pipeline hands
that same dict to after-tool callbacks and records it on the
execute_tool span as gcp.vertex.agent.tool_call_args (content capture is
on by default), so the credential ended up in traces and in whatever
after-tool plugins record. This affected every OpenAPIToolset,
APIHubToolset, GoogleApiToolset and ApplicationIntegrationToolset tool.
IntegrationConnectorTool did the same with the end user's OAuth access
token, and also logged it at INFO.

Work on a copy of args in both places, as AuthenticatedFunctionTool
already does, and log the connector's args before the token is added.

Fixes google#7162

Claude-Session: https://claude.ai/code/session_013vXxD1ga1hnCq2uFRwNks7
@1aifanatic
1aifanatic force-pushed the fix/rest-api-tool-credential-in-tool-args branch from d16c929 to aacd3ca Compare September 18, 2026 03:41
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.

RestApiTool writes the tool's API key / OAuth token into the caller's args, so it lands in trace spans and after-tool callbacks

2 participants