fix(sts): key the ADK token cache by acting subject, not session alone (Python)#2333
Draft
QuentinBisson wants to merge 2 commits into
Draft
fix(sts): key the ADK token cache by acting subject, not session alone (Python)#2333QuentinBisson wants to merge 2 commits into
QuentinBisson wants to merge 2 commits into
Conversation
The Python token-propagation plugin cached the exchanged token under the session id, so a session carrying messages from more than one subject reused whichever subject's token seeded the cache first. Every later caller's MCP tool calls then ran under that first subject's delegated identity. The acting subject is now resolved from session state before the cache lookup and folded into the key as the issuer-scoped sub claim, falling back to a hash of the raw token for opaque or sub-less tokens. after_run_callback sweeps every expired entry, since a session now holds one per subject and only the acting subject's key is derivable there. Signed-off-by: QuentinBisson <quentin@giantswarm.io>
…y note Signed-off-by: QuentinBisson <quentin@giantswarm.io>
QuentinBisson
force-pushed
the
fix/py-sts-cache-per-subject
branch
from
July 25, 2026 13:44
3b591e5 to
be7a504
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What
The Python ADK token-propagation plugin cached the exchanged (delegated) token
keyed by session ID alone. A session that carries messages from more than one
subject therefore reused whichever subject's token seeded the cache first, so
every later caller's MCP tool calls ran under that first subject's delegated
identity.
This is the Python counterpart of #2317, which fixes the same defect in the Go
ADK runtime. The two runtimes had the same cache shape and the same bug.
Changes
cache_keyis nowsession.idplus the acting subject._subject_key()derives the discriminator from the caller's token as
iss+sub(subisonly unique within an issuer), or a hash of the raw token when there is no
sub. The token is parsed unverified and used only to partition the cache,never to gate a security decision; it is validated server-side on exchange.
before_run_callbackresolves the subject token before the cache lookupinstead of after it, so the lookup can be keyed by subject at all. The
resolution is centralized in
_read_subject_tokenand reused rather thanrepeated.
after_run_callbacksweeps every expired entry. A session now holds one entryper subject, and only the acting subject's key is derivable there, so the
previous single-key eviction would leave other subjects' expired entries
behind.
Both the write path (
before_run_callback) and the read path(
header_provider) resolve the subject fromsession.state, which_agent_executor.pyrewrites from the current request on every normal run. Noequivalent of the Go fix's CallContext fallback is needed here.
Behavior change
Same shape as #2317: in a read-write shared session, a visitor's tool calls move
from the owner's exchanged token to the visitor's own. That corrects an identity
collapse rather than introducing one, but it is externally observable.
A request that carries no resolvable subject token no longer reuses a token the
session cached earlier; it skips propagation. That is the fail-closed half of
keying by subject.
Known gap, not addressed here
_agent_executor.pyrefreshessession.state["headers"]only on the normal runbranch. The HITL-resume branch skips it, so a resume keeps the previous request's
headers and the subject resolves to the original caller. If a different user
approves an interrupted run, they get the original caller's token. Per-subject
keying cannot see that, because the approver's identity never reaches session
state. Fixing it means writing headers on the HITL branch too, in
kagent-adkrather than
agentsts-adk.Tests
Adds six cases: two subjects in one session keep separate tokens and each gets
its own back from
header_provider; the same subject twice exchanges once; thesame
subat two issuers does not collide; opaque and sub-less tokens stillpartition via the hash branch; an expired entry belonging to a subject other
than the acting one is still evicted; and a tokenless caller in a session that
already holds a cached token gets nothing back from
header_provider(thefail-closed read path).
Verified the new tests catch the original defect: with the key forced back to
session.idalone, two subjects collapse to one cache entry and one exchange,with the second caller inheriting the first's token.
The 57 existing plugin tests assert on cache contents keyed by session id and
are updated to key through
plugin.cache_key(...)so they no longer pin the keyformat.