Skip to content

Retire access tokens leaked before the server:clients fix (GRYT-1259) - #195

Merged
sivert-io merged 1 commit into
mainfrom
claude/GRYT-1259-token-version-bump
Sep 15, 2026
Merged

sivert-io merged 1 commit into
mainfrom
claude/GRYT-1259-token-version-bump

Conversation

@sivert-io

Copy link
Copy Markdown
Member

Review-required path

This adds a migration under packages/server/src/db/** (persistence), so it needs a read of the whole diff before it merges. The changed files are the new tokenReissueMigration.ts, its test, and one call plus one import wired into connection.ts.

Why

server#194 (GRYT-1239) stops server:clients and server:details.clients from leaking every member's live access token, but it cannot recall a token that already leaked. While the bug was live an attacker could roll a captured access token forward indefinitely by refreshing inside each fifteen-minute window, so an upgrade that only stops new leaks still leaves the captured ones working.

What it does

reissueAccessTokensAfterLeak bumps server_config.token_version by one, once, inside runMigrations. That counter is what requireAuth, session restore and token:refresh compare a token against, so every access token minted before the bump is refused from then on.

  • Fires once. A schema_meta marker (access_token_reissue_gryt1239) records that it ran, in the same transaction as the bump, so a later boot never repeats it.
  • Skips a fresh install. It only bumps a database that already has member rows. A server nobody ever joined never issued a token to leak, so there is nothing to retire; the marker is still written so it never reconsiders.
  • Refresh tokens keep working. The bump touches only server_config. A live client whose access token is now refused falls back to its refresh token and re-mints at the new version on its own, so the sign-out is a brief reconnect rather than a re-login. Refresh tokens were never exposed by the leak.
  • One log line names the new counter value when it fires.

Tests

tokenReissueMigration.test.ts:

  • bumps the counter once on a server that had members;
  • does nothing on the second boot;
  • spares a fresh install that never had a member;
  • an old access token is refused at requireAuth and at token:refresh afterwards;
  • a refresh token still mints a new access token, and that new token is accepted where the old one was refused.

Verified end to end on a throwaway server: joined on origin/main (the code before this migration), captured the access and refresh tokens, then restarted onto this branch against the same data directory. The migration bumped token_version 0 to 1 and logged one line; the captured access token came back token:revoked (token_version_mismatch) and got no session; the refresh token minted a fresh access token; the fresh token restored the session. A second restart did not bump again.

Local CI: yarn test (1336), yarn test:examples (9), npx tsc --noEmit, yarn build, npx eslint ., node scripts/check-comment-length.mjs, all clean.

Release

This is the recall half of the server:clients fix. It should ship together with server#194 (GRYT-1239), and with #192 (GRYT-1238) and #193 already on main.

🤖 Generated with Claude Code

Before GRYT-1239, every member's live access token reached every other
member through server:clients, and a token that leaked can't be
recalled. This bumps server_config.token_version once, on the upgrade
that carries the fix, so every access token minted before it stops
passing requireAuth and token:refresh.

It runs as a schema_meta-marked one-shot in runMigrations, so it fires
once and never again. It skips a database nobody has joined, since a
fresh install never issued a token to leak, and still writes the marker
there so a later boot never reconsiders. One log line when it fires.

Refresh tokens are untouched, so a live client falls back to its refresh
token and re-mints at the new version on its own. Only a captured access
token stops working.

Verified on a throwaway server: joined on the old code, captured the
token, restarted onto this branch. The migration bumped the counter and
logged once, the old token was refused at requireAuth and token:refresh,
the refresh token minted a new one, and the new one restored the session.
A second restart did not bump again.

Co-Authored-By: Claude Opus 5 <[email protected]>
@sivert-io
sivert-io merged commit b0accbf into main Sep 15, 2026
3 checks passed
@sivert-io
sivert-io deleted the claude/GRYT-1259-token-version-bump branch September 15, 2026 21:09
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant