From 1cd6f464e5b8ececa957928ca67d65145be558ab Mon Sep 17 00:00:00 2001 From: prasanna8585 <65734642+prasanna8585@users.noreply.github.com> Date: Wed, 19 Aug 2026 00:15:28 -0700 Subject: [PATCH] fix: redact credentials from generate_content_config.http_options in debug logs Merge https://github.com/google/adk-python/pull/6546 PiperOrigin-RevId: 967026896 --- src/google/adk/models/google_llm.py | 22 +++++++++--- tests/unittests/models/test_google_llm.py | 42 +++++++++++++++++++++-- 2 files changed, 57 insertions(+), 7 deletions(-) diff --git a/src/google/adk/models/google_llm.py b/src/google/adk/models/google_llm.py index dd27d42c..5713ef0d 100644 --- a/src/google/adk/models/google_llm.py +++ b/src/google/adk/models/google_llm.py @@ -717,14 +717,28 @@ def _build_request_log(req: LlmRequest) -> str: exclude={ 'system_instruction': True, 'tools': tools_exclusion if req.config.tools else True, - # Callers may put credentials in per-request headers, so the - # transport options never go to the log. - 'http_options': True, + # `http_options` carries caller-supplied credentials: + # `headers` commonly holds an Authorization bearer token, + # and `extra_body` / `*client_args` are free-form + # passthroughs that can hold auth material too. None of + # it may reach a debug log. Mirrors the same exclusion + # applied to trace spans in telemetry/tracing.py. + 'http_options': { + 'httpx_client': True, + 'httpx_async_client': True, + 'aiohttp_client': True, + 'headers': True, + 'extra_body': True, + 'client_args': True, + 'async_client_args': True, + }, }, ) ) except Exception: - config_log = repr(req.config.model_copy(update={'http_options': None})) + # Do not fall back to repr(req.config) here: an unredacted repr would + # reintroduce the same credential leak this function exists to avoid. + config_log = '' return f""" LLM Request: diff --git a/tests/unittests/models/test_google_llm.py b/tests/unittests/models/test_google_llm.py index d092be44..c6c31a74 100644 --- a/tests/unittests/models/test_google_llm.py +++ b/tests/unittests/models/test_google_llm.py @@ -2563,7 +2563,7 @@ def test_build_request_log_function_declarations_in_second_tool(): def test_build_request_log_fallback_to_repr_on_all_failures(monkeypatch): - """Test that _build_request_log falls back to repr() if model_dump fails.""" + """Test that _build_request_log falls back to placeholder if model_dump fails.""" llm_request = LlmRequest( model="gemini-2.5-flash", @@ -2584,9 +2584,45 @@ def test_build_request_log_fallback_to_repr_on_all_failures(monkeypatch): log_output = _build_request_log(llm_request) - # Should still succeed using repr() + # Should still succeed using the placeholder assert "Config:" in log_output - assert "GenerateContentConfig" in log_output + assert "" in log_output + + +def test_build_request_log_redacts_http_options_credentials(): + """Test that _build_request_log redacts sensitive fields in http_options.""" + llm_request = LlmRequest( + model="gemini-2.5-flash", + contents=[Content(role="user", parts=[Part.from_text(text="Hello")])], + config=types.GenerateContentConfig( + temperature=0.7, + http_options=types.HttpOptions( + headers={"Authorization": "Bearer secret_token"}, + extra_body={"secret_key": "some_secret"}, + client_args={"token": "arg_secret"}, + async_client_args={"token": "async_secret"}, + base_url="https://example.com/api", + ), + ), + ) + + log_output = _build_request_log(llm_request) + + assert "Config:" in log_output + # base_url should be present + assert "https://example.com/api" in log_output + # sensitive http_options fields should NOT be present in log_output + assert "secret_token" not in log_output + assert "secret_key" not in log_output + assert "arg_secret" not in log_output + assert "async_secret" not in log_output + assert "'headers'" not in log_output + assert "'extra_body'" not in log_output + assert "'client_args'" not in log_output + assert "'async_client_args'" not in log_output + assert "'httpx_client'" not in log_output + assert "'httpx_async_client'" not in log_output + assert "'aiohttp_client'" not in log_output @pytest.mark.asyncio