# A WebSocket reconnect is not a standing authorization grant

Clients drop and reconnect all the time. Replaying the same ticket, cookie, or “already authorized” flag from the previous socket is how revoked users keep receiving live updates they should no longer see.

## The bug pattern

A common flow:

1. The first connection authenticates and runs `authorize(actor, subscribe, resource)`.
2. The server attaches a long-lived channel binding: room membership, subscription id, or an in-memory “allowed topics” set for that socket.
3. The network blips. The client reconnects with the old session token, refresh cookie, or reconnect ticket.
4. The server restores the previous subscriptions from cache or client-supplied topic list — and skips a fresh authorize call.

Between the first handshake and the reconnect, membership may have been revoked, the object may have moved tenants, or the grant may have expired. The restored channel did not notice.

## Why reconnect shortcuts fail

A successful prior connection answers *who was allowed then*, not *who is allowed now*.

- Reconnect tickets are often treated as proof of identity *and* permission, even though they only prove the client once held a socket.
- Topic lists sent by the client (`rooms: ["doc:42"]`) are untrusted input. Presence on the list is not an allow decision.
- Sticky session state on a gateway can outlive a role demotion if the app never re-runs authorize on resume.
- Fan-out systems that key subscriptions by connection id may reattach the old id’s grants when a new connection claims continuity.

Live channels need the same freshness rules as HTTP reads — especially after any break in the connection.

## What to enforce

Treat every reconnect as a new authorization boundary:

- **Re-authenticate and re-authorize on resume.** Do not restore topic membership from the previous socket without a live `authorize` for each topic.
- **Ignore client-supplied topic lists as grants.** Use them only as *requests*; decide allow/deny from current policy and relationships.
- **Bind subscriptions to a principal version.** When roles, membership, or object ownership change, drop or rebuild active subscriptions for that actor — including ones that survived a reconnect.
- **Short-lived reconnect tickets.** If you issue a resume token, scope it tightly (actor, expiry, optional connection nonce) and still run authorize before reattaching sensitive topics.
- **Fail closed on ambiguity.** If policy or membership cannot be checked during resume, accept the socket but attach no privileged subscriptions until checks succeed.

## Tiny checklist

1. After removing a user from a workspace, does their next WebSocket reconnect still receive events for that workspace’s rooms?
2. On resume, do you call authorize per topic, or only verify that the session cookie is still valid?
3. Can a client reconnect and pass `subscribe: ["export:job:9"]` without a fresh object-level check?
4. When an object’s owner or tenant changes, which active socket subscriptions are torn down?

Reconnecting is normal. Restoring yesterday’s channel grants without a new allow decision is a standing permission you never meant to keep open.

