auth: scrub refresh-token slot on logout · Entire
auth: scrub refresh-token slot on logout
22b1052→main·toothbrush·1mo ago·4 files·+50 added/-7 removed
RemoveCurrentContext and RemoveAllContexts deleted only the access-token keychain slot, leaving the long-lived refresh token behind. After logout any keyring-capable process could still mint fresh access tokens from it. Delete both the access slot and its paired
Adds tokenstore.RefreshService() so the ':refresh' suffix lives in one place, and extends the logout tests to assert both slots are gone.
Co-Authored-By: Claude Opus 4.8 noreply@anthropic.com
Sessions
660b42be1060View transcript
Changes
4
cmd/entire/cli/auth
- Mcontext_store.go+10/-3
- Mcontexts.go+1/-1
- Mcontexts_test.go+30/-3
internal/entireclient/tokenstore
- Mtokenstore.go+9
// Best-effort keychain cleanup, sequenced off the context just removed.
// A missing entry is fine — the contexts.json removal above is what makes
// us "logged out".
// us "logged out". Both the access slot and its paired refresh slot must
// go: leaving the long-lived refresh token behind would let any later
// keyring-capable process mint fresh access tokens after logout.
if svc != "" && handle != "" {
_ = tokenstore.Delete(svc, handle) //nolint:errcheck // best-effort; contexts.json removal is the source of truth for logout
_ = tokenstore.Delete(svc, handle) //nolint:errcheck // best-effort; contexts.json removal is the source of truth for logout
_ = tokenstore.Delete(tokenstore.RefreshService(svc), handle) //nolint:errcheck // best-effort; absent refresh slot is fine
}
return nil
}`
for _, c := range f.Contexts {
if c.KeychainService != "" && c.Handle != "" {
_ = tokenstore.Delete(c.KeychainService, c.Handle) //nolint:errcheck // best-effort; the contexts.json clear below is authoritative
// Delete both slots: the access token and its paired refresh
// token. Dropping the refresh slot is what actually scrubs the
// machine — otherwise a leftover refresh token outlives logout.
_ = tokenstore.Delete(c.KeychainService, c.Handle) //nolint:errcheck // best-effort; the contexts.json clear below is authoritative
_ = tokenstore.Delete(tokenstore.RefreshService(c.KeychainService), c.Handle) //nolint:errcheck // best-effort; absent refresh slot is fine
}
removed++
}
**... (truncated for brevity) ...**
// RefreshService returns the paired refresh-token service name for an
// access-token service, following the "<service>:refresh" convention.
// Callers store the raw refresh token under (RefreshService(service), user) alongside the access token at (service, user).
func RefreshService(service string) string {
return service + ":refresh"
}