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.
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 IDBeforeRunCallbackcomputessessionID = ctx.Session().ID(), and on a cache hit returns the cached token without re-exchanging the current request's bearer:A single ADK/A2A session (
contextID) can legitimately receive messages from different subjects:UserIDCallInterceptorsetsCallContext.Userper request fromx-user-id, andwithBearerTokenreads theAuthorizationbearer per request. But once the first request seedstokenCache[sessionID], later requests with a different subject reuse that entry, so their MCP tool calls (viaheaderRoundTripper/ STSheaderProvider) 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.