Skip to main content
← Back to list
01Issue
BugOpenSwamp CLIPublic
AssigneesNone

Relationships

#2534 serve: without a remote datastore the token GC is not serialized against token rotate/re-mint

Opened by stack72 · 9/25/2026

Description

The server token GC (swamp-club#2413) collects each token as one unit under serve's sync gate. Inside the unit it re-reads the token's token-main, re-checks eligibility, then deletes the secret, the OAuth access token and the records. Serve's access.token.mint, access.token.rotate and access.token.revoke handlers, and the OAuth login mint, hold the same gate. With a remote datastore, a token write in the same process therefore lands either before the GC's re-read or after its unit.

Serve only creates the gate when a sync service exists. In src/cli/commands/serve.ts it is created as syncService ? createSyncGate() : undefined. On the default filesystem datastore, withSyncGate(undefined, fn) just calls fn(), so nothing serializes the GC against token writes.

Race (filesystem datastore)

  1. Token ci expired two hours ago, and the grace period is one hour.
  2. A sweep re-reads ci, finds it eligible, and moves on to resolve its definition.
  3. An operator's access.token.rotate ci writes a new secret to server-token-ci and an active token-main on the same definition.
  4. The GC deletes server-token-ci (the new secret), then the definition and data.
  5. The rotate has already returned a credential that never authenticates, and ci is gone from access token list.

The window is a few milliseconds per collected token per sweep, and the failure is fail-closed (no unauthorized access), but the rotated token is lost and has to be minted again. design/primitives/serve.md documents this gap.

Expected

Token writes and the GC's collection unit are serialized in every deployment. That could be a process-local lock for token mutations, which the GC, the token handlers and the OAuth mint share whether or not a sync service exists. Or the sync gate could always be created for these units. Creating it unconditionally for every gated request would also serialize the other handlers on filesystem serve, which may or may not be wanted.

02Bog Flow
◉OPEN○TRIAGED○IN PROGRESS○SHIPPED

Open

9/25/2026, 3:43:58 PM

No activity in this phase yet.

03Sludge Pulse

Sign in to post a ripple.