You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
[Server] Bind Streamable HTTP sessions to the authenticated principal #529
Currently a stateful StreamableHttpTransport session is resolved only by its Mcp-Session-Id header - Protocol::resolveSession() checks sessionManager->exists() and loads it with createWithId(), and DELETE goes straight to destroySession(). With AuthorizationMiddleware in front, every request is still authenticated with the caller's own token and scopes are not inherited, but the session doesn't know which principal created it. So a request with a different valid token and the same session ID is accepted into that session, DELETE included.
AuthorizationResult exposes a stable identity - issuer + subject rather than sub alone, maybe the client id as well
the transport passes that into session resolution
on initialize we store a fingerprint of it in the session, later POST/DELETE compare and answer 404 on mismatch
Open questions:
a) bind always when an identity is present, or opt-in via the builder?
b) does it belong into Protocol or rather the session manager / store, so a custom store can key on <principal>:<session id>?
c) what about validators that don't produce a subject (opaque tokens, API keys)?
Keeping the core unaware of OAuth would be nice, so probably a plain identity string rather than the auth result itself.
The invariant I would preserve is: the authorization layer derives one opaque, stable session-binding ID; the session layer enforces it for every load and destroy without interpreting OAuth claims.
That keeps Protocol OAuth-agnostic and also works for API keys/custom validators. For the built-in JWT path, a safe default looks like a canonical tuple derived from validated identity, e.g. issuer + subject, with client identity (client_id / azp) included only under an explicit, documented policy because those claims are not interchangeable across issuers. Persist a digest of that tuple, not raw claims. I would exclude the access token, expiry, and scopes so normal token refresh or scope changes do not orphan the session.
One fail-closed edge case seems important: once a session is created as bound, a later request with no binding must be treated exactly like a mismatch. Otherwise a custom validator that omits identity, or middleware accidentally reordered later, becomes a downgrade path. If a deployment requires binding, initialize should fail when its resolver cannot produce an ID; legacy unbound sessions should require an explicit compatibility mode rather than happen implicitly.
The current callback surface also suggests where enforcement has to land: processInput() can reach resolveSession(), but onSessionEnd / destroySession() carries only the UUID. A POST-only check in Protocol would therefore leave DELETE exposed. I would thread the opaque binding through both session resolution and teardown and make the manager/store boundary responsible—either binding-aware create/load/destroy operations or an atomic compound key such as <binding-digest>:<session-id>. That makes custom stores hard to bypass accidentally. Unknown, missing-binding, and mismatched-binding cases should remain indistinguishable externally (404).
A compact regression matrix:
same issuer/subject/client after token rotation: POST and DELETE succeed;
same sub, different issuer: both fail;
same issuer/subject, different client: outcome follows the configured client-binding policy;
missing binding cannot access or delete an already bound session;
a custom non-OAuth validator can supply a stable opaque ID;
failures never reveal whether the UUID exists.
AI assistance disclosure: ChatGPT was used to inspect the cited repository paths and draft this review. This is a source-level design analysis; I have not implemented or executed these tests.
Activity
jarrettdustinqq commentedon Oct 6, 2026
The invariant I would preserve is: the authorization layer derives one opaque, stable session-binding ID; the session layer enforces it for every load and destroy without interpreting OAuth claims.
That keeps
ProtocolOAuth-agnostic and also works for API keys/custom validators. For the built-in JWT path, a safe default looks like a canonical tuple derived from validated identity, e.g. issuer + subject, with client identity (client_id/azp) included only under an explicit, documented policy because those claims are not interchangeable across issuers. Persist a digest of that tuple, not raw claims. I would exclude the access token, expiry, and scopes so normal token refresh or scope changes do not orphan the session.One fail-closed edge case seems important: once a session is created as bound, a later request with no binding must be treated exactly like a mismatch. Otherwise a custom validator that omits identity, or middleware accidentally reordered later, becomes a downgrade path. If a deployment requires binding,
initializeshould fail when its resolver cannot produce an ID; legacy unbound sessions should require an explicit compatibility mode rather than happen implicitly.The current callback surface also suggests where enforcement has to land:
processInput()can reachresolveSession(), butonSessionEnd/destroySession()carries only the UUID. A POST-only check inProtocolwould therefore leave DELETE exposed. I would thread the opaque binding through both session resolution and teardown and make the manager/store boundary responsible—either binding-aware create/load/destroy operations or an atomic compound key such as<binding-digest>:<session-id>. That makes custom stores hard to bypass accidentally. Unknown, missing-binding, and mismatched-binding cases should remain indistinguishable externally (404).A compact regression matrix:
sub, different issuer: both fail;AI assistance disclosure: ChatGPT was used to inspect the cited repository paths and draft this review. This is a source-level design analysis; I have not implemented or executed these tests.