Skip to main content
← Back to list
01Issue
BugShippedSwamp CLIPublic
Assigneeshammz

Relationships

#2482 server tokens: concurrent mints of the same name can pair one mint's record with the other's secret, so a credential authenticates as the wrong principal

Opened by skunk-ape · 9/24/2026· Shipped 10/5/2026

Summary

Two concurrent mints of the same server token name can leave a token record from one mint paired with the secret from the other. The credential then authenticates as the wrong principal. Nothing prevents the race or detects the mismatch.

This came up while planning #2466. It predates that fix and affects every mint path.

How it happens

mint in src/domain/models/access/server_token_model.ts is check-then-act:

  1. It reads token-main and refuses if an active token exists.
  2. It writes the plaintext to the vault under serverTokenSecretKey(name), which is server-token-<name>, one key per name.
  3. It writes the token-main record, which holds the principalId.

Steps 2 and 3 are separate writes to separate stores (the _token-secrets control-plane store, and versioned data), each last-writer-wins. If mints A and B for the same name interleave as secret A, secret B, record B, record A:

  • The final record is A (principal A), and the final secret is B.
  • The holder of credential B presents <name>.<secret B>. It matches the vault, and the record says principal A, so B authenticates as principal A.
  • The holder of credential A is rejected.

rotate has the same shape (vault put, then record write).

Where it can happen

  • Two serve replicas, or a --server mint racing a local CLI mint on the serve host.
  • The OAuth device-login mint (device_auth_handler.ts) racing either of those for the same token name.
  • Within one serve process, the sync gate serialises handlers, so a single replica is not exposed on its own.
  • The CLI takes a model lock only when the definition already exists (acquireModelLocks keyed by model id). A first mint has no id yet, so it is unlocked even locally. Two concurrent first mints may also create two auto-definitions with the same name. That part is from reading the code, not verified.

Impact

Both racing callers must be admins, who can already mint a token for any principal, so there is no privilege escalation. It is a correctness and audit problem: a credential authenticates as a principal other than the one its minter asked for, and one mint silently loses.

Options considered

  • Fingerprint in the record. Store a hash of the plaintext in token-main and check it at auth. This detects the mismatched pairing and fails closed, but one mint is still lost, and its holder gets a confusing rejection.
  • Per-mint secret key in the record. For example server-token-<name>-<mintId>, recorded in secretKey. The record then always points at its own secret, so the last complete mint wins cleanly and the loser simply fails. This touches the code that assumes serverTokenSecretKey(name): reveal, revoke, GC, the boot consistency sweep, and migration.
  • Name-keyed lock around mint and rotate. This is the only option that prevents the race. It needs a datastore lock keyed by token name rather than model id, so it covers a first mint.

These combine well: a lock to prevent the race, plus a per-mint key (or a fingerprint) as defence in depth. All of them change the ServerToken aggregate, so existing records without the new field need a compatibility path.

02Bog Flow
✓OPEN✓TRIAGED✓IN PROGRESS✓SHIPPED+ 1 MOREASSIGNED+ 5 MOREREVIEW+ 10 MOREPR_MERGED+ 2 MORESESSION_SUMMARIZED

Shipped

10/5/2026, 10:25:25 PM

Click a lifecycle step above to view its details.

03Sludge Pulse
hammz assigned hammz10/5/2026, 6:23:39 PM

Sign in to post a ripple.