google / google/adk-python

Security Critical: Client_secret is shared in OAuth2 flow with authenticated function tool

Offen
#3,845 5 Kommentare 0 Reaktionen 1 zugewiesene Person Zugewiesen an @seanzhou1023 Auf GitHub ansehen
auth needs review
Vorherrschende Sprache
Python
Sterne
21.5k
Forks
4k
Ø Merge
1 T. 14 Std.
Gemergte PRs (30 T.)
37

Beschreibung

**Describe the bug**
The protocol publicly share the client_secret to the ADK client. This information MUST stay confidential and never shared

**To Reproduce**
1. Create a simple agent, with a simple tool, and use authenticated function tool
```
from google.adk.agents.llm_agent import Agent
from fastapi.openapi.models import OAuth2, OAuthFlows, OAuthFlowAuthorizationCode
from google.adk.auth import AuthCredential, AuthConfig, AuthCredentialTypes, OAuth2Auth
from google.adk.tools import ToolContext
from google.adk.tools.authenticated_function_tool import AuthenticatedFunctionTool

YOUR_OAUTH_CLIENT_ID = "513069150666-mkspihfonim1j4f0mfp8j2v6hkdtep6k.apps.googleusercontent.com"
YOUR_OAUTH_CLIENT_SECRET = "GOCSPX-

def log_tool(prompt: str, tool_context: ToolContext) -> None:
"""
Log the user query with the log_tool

:param prompt: The user query to log
:param tool_context: The tool context to access credentials

:return: None

"""

print(f"DEBUG: Received prompt: {prompt}")

headers = {}

# Retrieve the credential using the context and auth config
exchanged_credential = tool_context.get_auth_response(oauth_config)
if not exchanged_credential:
exchanged_credential = tool_context.get_auth_response(oidc_config)

if exchanged_credential and exchanged_credential.oauth2:
# Log the token for debug purposes as requested
print(f"DEBUG: Found credential for user {tool_context.user_id}")

access_token = exchanged_credential.oauth2.access_token

print(f"DEBUG: Access Token: {access_token}")

else:
print("DEBUG: No credential found in tool_context")


oauth_scheme = OAuth2(
flows=OAuthFlows(
authorizationCode=OAuthFlowAuthorizationCode(
authorizationUrl="https://accounts.google.com/o/oauth2/auth",
tokenUrl="https://oauth2.googleapis.com/token",
scopes={
"https://www.googleapis.com/auth/cloud-platform": "BigQuery"
},
)
)
)

oauth_credential = AuthCredential(
auth_type=AuthCredentialTypes.OAUTH2,
oauth2=OAuth2Auth(
client_id=YOUR_OAUTH_CLIENT_ID,
client_secret=YOUR_OAUTH_CLIENT_SECRET
),
)

oauth_config = AuthConfig(
auth_scheme=oauth_scheme,
raw_auth_credential= oauth_credential
)

oauth_authenticated_log_tool = AuthenticatedFunctionTool(func=log_tool, auth_config=oauth_config, response_for_auth_required="Pending User Authorization.")

root_agent = Agent(
model='gemini-2.5-flash',
name='root_agent',
description='A helpful assistant for user questions.',
instruction='Answer user questions to the best of your knowledge. Log the user query with the log_tool',
tools=[oauth_authenticated_log_tool]
)

```
2. Create a session
3. Invoke the secure tool

Sample of ADK response to indicate to the client to run a OAuth2 flow
```
[
{
"modelVersion": "gemini-2.5-flash",
"content": {
"parts": [
{
"text": "Okay, let's break this down. First, I'm starting with a very simple request, in French, to get information: \"Hello, a quel projet j'ai accès?\" That's my way of asking, \"Hello, which project do I have access to?\" Pretty straightforward, really.\n\nNow, from an architectural standpoint, I know that translates directly into the capabilities of the `list_project_tool`. That tool *is* designed to tell me exactly that: which Google Cloud projects are available to me. And the beauty of this is its simplicity – `list_project_tool` doesn't need any additional parameters; it just does what it says on the tin. So, my action is clear: I need to invoke the `list_project_tool` immediately to get the project list and provide a rapid answer. This is a core, fundamental action.\n",
"thought": true
},
{
"functionCall": {
"id": "adk-278007a2-d9c0-4f42-9be3-813ade149f37",
"args": {},
"name": "list_project_tool"
},
"thoughtSignature": "CpoDAePx_16-kKCcDZLmBIV4VfmwSfm1hqaUlGKD8fEbq-gEZlt60Xr0pGKHyutmyUR3ICd_Mv5NNP2QIxfSx4qrAE9blXQG4YQesJPe0bxM3IiXZRn2FnclS7xBbRi9RfaFCGzJx1Hp_g4WQDaNZIWqaNSb7aM8Cunx7B1EXPHgNbvIJ3IeIsXEVBd24VGJmpWwjRXtbLOvFiPkRwqk4ryl27Eu3AJwzb2uIRXWGtGWdp4JpNW0nGbRgiZ7By25uVGi6ajw2TGXc8gbOmt-5Jw8yjF2ZHSNcJqcqVIT62jCw6S7ViOhrtYjGW4mw5iV4NqjLOd6HE3v4m2Kemohwqz_-6qftz7hyPiFuOFT3QAWu0SOdVC4b3UX97eBgmzikJbPuvcgYzMpMM4cXoG_19JBEii-Ih4SbfVbxoXj-v4iEU2RHvOiDocC3IXpqnTng_MsuacTLhVbd3xndeexkZNu4yqW94AH5dKKytCmM81n-DQDrEe-WuC2l-O3xTN-AUz4nlg7GlmwPN1fUAxfgTuGoByQSayiDcLZH2g="
}
],
"role": "model"
},
"finishReason": "STOP",
"usageMetadata": {
"candidatesTokenCount": 5,
"candidatesTokensDetails": [
{
"modality": "TEXT",
"tokenCount": 5
}
],
"promptTokenCount": 156,
"promptTokensDetails": [
{
"modality": "TEXT",
"tokenCount": 156
}
],
"thoughtsTokenCount": 86,
"totalTokenCount": 247,
"trafficType": "ON_DEMAND"
},
"avgLogprobs": -1.7120042800903321,
"invocationId": "e-7a667312-57eb-4955-965f-b4faf1302ce2",
"author": "space_agent",
"actions": {
"stateDelta": {},
"artifactDelta": {},
"requestedAuthConfigs": {},
"requestedToolConfirmations": {}
},
"longRunningToolIds": [],
"id": "734f13d7-d323-4a8a-bff1-da7fde72109e",
"timestamp": 1764943012.128835
},
{
"content": {
"parts": [
{
"functionCall": {
"id": "adk-4cab06d4-397d-4013-ab7b-d1ee3bafac12",
"args": {
"functionCallId": "adk-278007a2-d9c0-4f42-9be3-813ade149f37",
"authConfig": {
"authScheme": {
"type": "oauth2",
"flows": {
"authorizationCode": {
"scopes": {
"https://www.googleapis.com/auth/cloud-platform": "BigQuery"
},
"authorizationUrl": "https://accounts.google.com/o/oauth2/auth",
"tokenUrl": "https://oauth2.googleapis.com/token"
}
}
},
"rawAuthCredential": {
"authType": "oauth2",
"oauth2": {
"clientId": "513069150666-mkspihfonim1j4f0mfp8j2v6hkdtep6k.apps.googleusercontent.com",
"clientSecret": "GOCSPX-"
}
},
"exchangedAuthCredential": {
"authType": "oauth2",
"oauth2": {
"clientId": "513069150666-mkspihfonim1j4f0mfp8j2v6hkdtep6k.apps.googleusercontent.com",
"clientSecret": "GOCSPX-",
"authUri": "https://accounts.google.com/o/oauth2/auth?response_type=code&client_id=513069150666-mkspihfonim1j4f0mfp8j2v6hkdtep6k.apps.googleusercontent.com&scope=https%3A%2F%2Fwww.googleapis.com%2Fauth%2Fcloud-platform&state=0eEsoJ0HwJynIGynZ0GtOZmAiJWsTz&access_type=offline&prompt=consent",
"state": "0eEsoJ0HwJynIGynZ0GtOZmAiJWsTz"
}
},
"credentialKey": "adk_oauth2_-3537330412975045649_oauth2_-8782076668427256465"
}
},
"name": "adk_request_credential"
}
}
],
"role": "user"
},
"invocationId": "e-7a667312-57eb-4955-965f-b4faf1302ce2",
"author": "space_agent",
"actions": {
"stateDelta": {},
"artifactDelta": {},
"requestedAuthConfigs": {},
"requestedToolConfirmations": {}
},
"longRunningToolIds": [
"adk-4cab06d4-397d-4013-ab7b-d1ee3bafac12"
],
"id": "65986065-222e-4ab2-b9b1-3b40e9adfa73",
"timestamp": 1764943015.619703
},
{
"content": {
"parts": [
{
"functionResponse": {
"id": "adk-278007a2-d9c0-4f42-9be3-813ade149f37",
"name": "list_project_tool",
"response": {
"result": "Pending User Authorization."
}
}
}
],
"role": "user"
},
"invocationId": "e-7a667312-57eb-4955-965f-b4faf1302ce2",
"author": "space_agent",
"actions": {
"stateDelta": {},
"artifactDelta": {},
"requestedAuthConfigs": {
"adk-278007a2-d9c0-4f42-9be3-813ade149f37": {
"authScheme": {
"type": "oauth2",
"flows": {
"authorizationCode": {
"scopes": {
"https://www.googleapis.com/auth/cloud-platform": "BigQuery"
},
"authorizationUrl": "https://accounts.google.com/o/oauth2/auth",
"tokenUrl": "https://oauth2.googleapis.com/token"
}
}
},
"rawAuthCredential": {
"authType": "oauth2",
"oauth2": {
"clientId": "513069150666-mkspihfonim1j4f0mfp8j2v6hkdtep6k.apps.googleusercontent.com",
"clientSecret": "GOCSPX-"
}
},
"exchangedAuthCredential": {
"authType": "oauth2",
"oauth2": {
"clientId": "513069150666-mkspihfonim1j4f0mfp8j2v6hkdtep6k.apps.googleusercontent.com",
"clientSecret": "GOCSPX-",
"authUri": "https://accounts.google.com/o/oauth2/auth?response_type=code&client_id=513069150666-mkspihfonim1j4f0mfp8j2v6hkdtep6k.apps.googleusercontent.com&scope=https%3A%2F%2Fwww.googleapis.com%2Fauth%2Fcloud-platform&state=0eEsoJ0HwJynIGynZ0GtOZmAiJWsTz&access_type=offline&prompt=consent",
"state": "0eEsoJ0HwJynIGynZ0GtOZmAiJWsTz"
}
},
"credentialKey": "adk_oauth2_-3537330412975045649_oauth2_-8782076668427256465"
}
},
"requestedToolConfirmations": {}
},
"id": "d2beef7d-abb7-499d-8929-d4e5c11da93c",
"timestamp": 1764943015.618363
},
{
"modelVersion": "gemini-2.5-flash",
"content": {
"parts": [
{
"text": "I need your authorization to list the projects you have access to. Could you please grant it?"
}
],
"role": "model"
},
"finishReason": "STOP",
"usageMetadata": {
"candidatesTokenCount": 19,
"candidatesTokensDetails": [
{
"modality": "TEXT",
"tokenCount": 19
}
],
"promptTokenCount": 358,
"promptTokensDetails": [
{
"modality": "TEXT",
"tokenCount": 442
}
],
"totalTokenCount": 377,
"trafficType": "ON_DEMAND"
},
"avgLogprobs": -0.19399306648655942,
"invocationId": "e-7a667312-57eb-4955-965f-b4faf1302ce2",
"author": "space_agent",
"actions": {
"stateDelta": {},
"artifactDelta": {},
"requestedAuthConfigs": {},
"requestedToolConfirmations": {}
},
"id": "889fbcf7-1075-41a1-9c8c-4e46aac5acfd",
"timestamp": 1764943015.620248
}
]

```

**Expected behavior**
Not to see the client_secret, or see it redacted

**Desktop (please complete the following information):**
- OS: Windows with WSL Ubuntu 24
- Python version(python -V): 3.12
- ADK version(pip show google-adk): 1.19.0

**Model Information:**
- Which model is being used(e.g. gemini-2.5-pro): Gemini 2.5 flash

**Additional context**
I didn't find a way to solve this easily. There are many classes to handle the authentication (credential service, credential manager, authHandler, oauth exchanger,...)

My wish is to have a in memory map with the client_id as a key, and the client secret as value.
When the function response comes in, the auth_config must be enriched with the client_secret, based on the client secret contained in the exchanged

Beitragsleitfaden

Beitragsleitfaden öffnen

Bewertung

Dieses Issue wurde noch nicht bewertet.

Neue Issues direkt in Ihr Postfach

Eine kurze Übersicht über anfängerfreundliche GitHub-Issues.