google / google/adk-python

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

Đang mở
#3,845 5 bình luận 0 reaction 1 người được giao Được giao cho @seanzhou1023 Xem trên GitHub
auth needs review
Ngôn ngữ chính
Python
Star
21.5k
Fork
4k
Merge trung bình
1 ngày 14 giờ
Pull request đã merge (30 ngày)
37

Mô tả

**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

Hướng dẫn đóng góp

Mở hướng dẫn đóng góp

Đánh giá

Issue này chưa được đánh giá.

Nhận issue mới trong hộp thư của bạn

Bản tóm tắt ngắn những issue GitHub phù hợp với người mới.