close
Skip to content

kirocrew logout does not revoke refresh chains (no global chain revocation) #2028

Description

@ptias

Follow-up split out of #2024 (documentation), at the design reviewer's suggestion.

Summary

kirocrew logout is documented in several places as ending all dashboard
sessions. It revokes access sessions only — refresh chains are a separate
mechanism and survive it, so a browser still holding a valid mc_refresh_<port>
cookie can obtain a fresh access cookie afterwards. Restarting the gateway does
not end them either, because the refresh cookie is self-contained and signed with
the persistent token_signing.key.

There is also no global refresh-chain revocation path: revoke_chain is
per-chain, and its only caller is POST /api/auth/logout, which requires the
target browser's own cookie. An operator has no server-side way to terminate a
chain they do not hold.

Why this matters beyond wording

docs/guides/remote-and-mobile.md recommends exposing the loopback dashboard
through a tunnel for phone access, and notes that token IP-pinning is ineffective
there (the pin binds to the tunnel process, which connects from loopback). With
pinning ineffective and rotation extending sessions to a 30-day sliding window,
kirocrew logout was the main remaining server-side control an operator had.

docs/guides/slack-setup.md had this in a token-exposure playbook ("run
kirocrew logout ... if a token is exposed"), so the incident path pointed at a
control that does not do what the reader expects.

Scope of this issue

Docs are already corrected in #2024 — that PR fixes remote-and-mobile.md,
slack-setup.md, dashboard-token-auth.md and cli.md to describe the actual
behaviour and point operators at what does work (revoke at the tunnel/proxy auth
layer, or sign out in that browser). This issue tracks the code change.

Per SECURITY.md this issue deliberately contains no reproduction steps; the
security-relevant detail and impact analysis were sent privately to
kiro-crew-security-support@amazon.com.

Candidate fixes

Two approaches, both small:

  1. Have revoke_all_sessions() also revoke refresh chains. Requires enumerating
    and marking every known chain, and cannot cover a chain absent from the
    persisted state.
  2. Preferred: include the revocation generation as a claim in the refresh
    token payload and reject stale generations during validation. This makes the
    existing generation counter authoritative over both cookie types at once,
    needs no chain enumeration, reuses an already-persisted value, and fails
    closed for chains the state file has never seen.

Either way, kirocrew logout's help text and the doc passages corrected in #2024
should be revisited to match whichever semantics land — three passages in that PR
describe the current limitation and should be simplified once it no longer exists.

Metadata

Metadata

Assignees

Labels

area: gatewayGateway boot, auth/portal, caller authenticationclaimedAn automated fix is in progresssecurityHardening, trust boundary, or sandbox escape

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions