Skip to content

[Security] ADK server exposes Oauth client_secret to untrusted clients #2128

@arryon

Description

@arryon

I have a concern around the security of the default ADK handling of tool authentication and would appreciate some validation on this.

The ADK app exposes the /run_sse endpoint where we can interact with the runner's run_async loop.
Tool authentication is arranged in such a way that we indicate a tool's required authentication and ADK handles the token exchange.
In case there is no active token, the special adk_request_credential flow is triggered, and in case of Oauth2 this sometimes requires the client to prompt the user with an interactive authorization flow.

The documentation also describes this process: https://google.github.io/adk-docs/tools/authentication/#2-handling-the-interactive-oauthoidc-flow-client-side

My concern is around how this interactive flow is documented and handled by default. If we inspect the payload that gets generated inside /run_sse, an Event is broadcasted that has a full serialization of the raw oauth credentials including client_id and client_secret.

This feels like bad practice to advertise as method to prompt an external client to try and authenticate a user in an external application. The documentation states "Agent Client application interacts with ADK's fastapi server via /run or /run_sse endpoint. While ADK's fastapi server could be setup on the same server or different server as Agent Client application".

In auth, we should never expose client secrets to any external client we cannot trust, and front-end clients are among those. I think the documentation on how to handle the tool authentication in any client is not informing the user of the security risk, and we should pro-actively design a method that does not expose client secrets via /run_sse. One such approach might be to add a dedicated endpoint to the ADK FastAPI app that initiates the sign in, sends only the authorization URL to the client, and facilitates requesting new tokens from the third party oauth endpoint, instead of expecting the client to provide updated credentials.

Example payload JSON:

{
  "content": {
    "parts": [
      {
        "video_metadata": null,
        "thought": null,
        "inline_data": null,
        "file_data": null,
        "thought_signature": null,
        "code_execution_result": null,
        "executable_code": null,
        "function_call": {
          "id": "adk-690c1244-649c-4a65-afed-13ba46a5bec1",
          "args": {
            "functionCallId": "adk-78fb4da1-6bf8-4561-b478-4115962637c2",
            "authConfig": {
              "authScheme": {
                "type": "oauth2",
                "flows": {
                  "authorizationCode": {
                    "scopes": {
                      "read": "read",
                      "offline_access": "offline_access"
                    },
                    "authorizationUrl": "https://hostname/oauth2/auth",
                    "tokenUrl": "https://hostname/oauth2/token"
                  }
                }
              },
              "rawAuthCredential": {
                "authType": "oauth2",
                "oauth2": {
                  "clientId": "[full client id]",
                  "clientSecret": "[full client secret]"
                }
              },
              "exchangedAuthCredential": {
                "authType": "oauth2",
                "oauth2": {
                  "clientId": "[full client secret]",
                  "clientSecret": "[full client id]",
                  "authUri": "https://hostname/oauth2/auth?response_type=code&client_id=[client_id]&scope=read+offline_access&state=[state]&access_type=offline&prompt=consent",
                  "state": "..."
                }
              },
              "credentialKey": "[key]"
            }
          },
          "name": "adk_request_credential"
        },
        "function_response": null,
        "text": null
      }
    ],
    "role": "user"
  },
  "grounding_metadata": null,
  "partial": null,
  "turn_complete": null,
  "error_code": null,
  "error_message": null,
  "interrupted": null,
  "custom_metadata": null,
  "usage_metadata": null,
  "invocation_id": "e-7c873b32-cded-4f3a-b06e-1d0ee70ca618",
  "author": "agent",
  "actions": {
    "skip_summarization": null,
    "state_delta": {},
    "artifact_delta": {},
    "transfer_to_agent": null,
    "escalate": null,
    "requested_auth_configs": {}
  },
  "long_running_tool_ids": [
    "adk-690c1244-649c-4a65-afed-13ba46a5bec1"
  ],
  "branch": null,
  "id": "6xDQIdRj",
  "timestamp": 1753260170.005639
}

Let me know what you think please! Cheers

Metadata

Metadata

Assignees

Labels

tools[Component] This issue is related to tools

Type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions