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
After a user disconnects a delegated MCP account (Connected Accounts / DELETE /mcp/connections/<ref> on the platform), the agent keeps acting as that user for up to the access-token lifetime (~1h for Atlassian) — a new execution kept returning Jira results well after the account was disconnected (field report).
Root cause
Forge's managed delegated path (forge-core/mcp/server.gocase "user" → newDelegatedTokenSource) caches the per-subject access token in an internalSubjectTokenStore, TTL = the platform's expires_in (platform_token.gostore.Put(subject, tok, ttl)). Get returns it until expiry.
A platform disconnect only deletes the vault grant (agent-builder handleDeleteMCPConnection → DeleteUserGrant). It has no channel to forge's in-memory cache, and SubjectTokenStore.Evict is never called on the managed path. The provider's access token is also still valid at the provider, so nothing downstream fails either. Net: the cached token keeps working until its TTL lapses.
Note forge fetches from the platform token endpoint, which is the grant authority — after disconnect it returns 403/404 for that subject. Forge just isn't asking often enough.
Fix (this PR) — bound the window
Cap the delegated per-subject token cache TTL to a short revoke window (default 5 min), so forge re-validates against the platform within that bound regardless of the provider token's longer lifetime. After disconnect, the next re-fetch (≤ cap) gets ErrNoToken from the platform and the call is denied — and with #377 (call-time auth gate) it parks instead of erroring. Reduces the disconnect window from ~1h to ≤ the cap. Self-contained; no cross-repo change; transparent to connected users (just a more frequent, cheap platform token call for active user-mode subjects).
A platform→forge POST /mcp/revoke {subject, server} (companion to /mcp/consent) evicting the token cache + pooled connection immediately on disconnect. Requires:
subjectConnPool.EvictSubject(subject) (today Evict reads the subject from ctx only),
a Manager.Revoke(server, subject) + the endpoint/handler/auth,
agent-builder fan-out on disconnect (mirrors signalConsent — ListAgentIDsUsingTool + a postAgentRevoke).
Plus downstream 401/403 eviction for provider-side revocation (a different scenario from platform disconnect).
Symptom
After a user disconnects a delegated MCP account (Connected Accounts /
DELETE /mcp/connections/<ref>on the platform), the agent keeps acting as that user for up to the access-token lifetime (~1h for Atlassian) — a new execution kept returning Jira results well after the account was disconnected (field report).Root cause
Forge's managed delegated path (
forge-core/mcp/server.gocase "user"→newDelegatedTokenSource) caches the per-subject access token in an internalSubjectTokenStore, TTL = the platform'sexpires_in(platform_token.gostore.Put(subject, tok, ttl)).Getreturns it until expiry.A platform disconnect only deletes the vault grant (
agent-builder handleDeleteMCPConnection→DeleteUserGrant). It has no channel to forge's in-memory cache, andSubjectTokenStore.Evictis never called on the managed path. The provider's access token is also still valid at the provider, so nothing downstream fails either. Net: the cached token keeps working until its TTL lapses.Note forge fetches from the platform token endpoint, which is the grant authority — after disconnect it returns 403/404 for that subject. Forge just isn't asking often enough.
Fix (this PR) — bound the window
Cap the delegated per-subject token cache TTL to a short revoke window (default 5 min), so forge re-validates against the platform within that bound regardless of the provider token's longer lifetime. After disconnect, the next re-fetch (≤ cap) gets
ErrNoTokenfrom the platform and the call is denied — and with #377 (call-time auth gate) it parks instead of erroring. Reduces the disconnect window from ~1h to ≤ the cap. Self-contained; no cross-repo change; transparent to connected users (just a more frequent, cheap platform token call for active user-mode subjects).Follow-up — instant revocation (separate issue/PR)
A platform→forge
POST /mcp/revoke {subject, server}(companion to/mcp/consent) evicting the token cache + pooled connection immediately on disconnect. Requires:SubjectTokenStoreexternally reachable (today it's a private per-source memStore; the standalone Standalone MCP consent delivery: build authorize URL + deliver login link (#317 follow-up) #332 path already uses the sharedManagerDeps.SubjectStore),subjectConnPool.EvictSubject(subject)(todayEvictreads the subject from ctx only),Manager.Revoke(server, subject)+ the endpoint/handler/auth,signalConsent—ListAgentIDsUsingTool+ apostAgentRevoke).Plus downstream 401/403 eviction for provider-side revocation (a different scenario from platform disconnect).
Related