Skip to content

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

@guillaumeblaquiere

Description

@guillaumeblaquiere

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


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]   
)

  1. Create a session
  2. 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-<redacted>"
                  }
                },
                "exchangedAuthCredential": {
                  "authType": "oauth2",
                  "oauth2": {
                    "clientId": "513069150666-mkspihfonim1j4f0mfp8j2v6hkdtep6k.apps.googleusercontent.com",
                    "clientSecret": "GOCSPX-<redacted>",
                    "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-<redacted>"
            }
          },
          "exchangedAuthCredential": {
            "authType": "oauth2",
            "oauth2": {
              "clientId": "513069150666-mkspihfonim1j4f0mfp8j2v6hkdtep6k.apps.googleusercontent.com",
              "clientSecret": "GOCSPX-<redacted>",
              "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

Metadata

Metadata

Labels

Type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions