Skip to content

STS token cache keyed by session ID collapses per-user identity in shared sessions #2181

Description

@QuentinBisson

Summary

The STS token-propagation plugin caches the exchanged (delegated) token keyed by session ID, so a session shared by multiple human users runs every user's tool calls under the first user's delegated identity.

Detail

go/adk/pkg/sts/plugin.go:

  • tokenCache map[string]*TokenCacheEntry // keyed by session ID
  • BeforeRunCallback computes sessionID = ctx.Session().ID(), and on a cache hit returns the cached token without re-exchanging the current request's bearer:
if entry, ok := p.getCachedToken(sessionID); ok {
    return nil, nil // uses cached token
}
// ... otherwise extract bearer, exchange, setCachedToken(sessionID, ...)

A single ADK/A2A session (contextID) can legitimately receive messages from different subjects: UserIDCallInterceptor sets CallContext.User per request from x-user-id, and withBearerToken reads the Authorization bearer per request. But once the first request seeds tokenCache[sessionID], later requests with a different subject reuse that entry, so their MCP tool calls (via headerRoundTripper / STS headerProvider) execute under the first subject's exchanged token. On cache expiry the next request re-seeds it, so attribution can also drift nondeterministically.

Proposed fix

Key the exchanged-token cache by sessionID + subject (the incoming bearer's/x-user-id's subject), or bypass the cache when the incoming subject differs from the cached entry's. That lets a shared session multiplex per-user identity and lets downstreams drop the consent workaround.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Fields

    No fields configured for issues without a type.

    Projects

    Status
    Backlog

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions