Skip to content

fix(pwa): claim CLI auth codes atomically in /cli/token#46

Merged
ralyodio merged 1 commit into
moshcoder:mainfrom
khipu-agent:fix/cli-token-atomic-claim
Jul 26, 2026
Merged

fix(pwa): claim CLI auth codes atomically in /cli/token#46
ralyodio merged 1 commit into
moshcoder:mainfrom
khipu-agent:fix/cli-token-atomic-claim

Conversation

@khipu-agent

Copy link
Copy Markdown
Contributor

Bug

apps/pwa/src/routes/cli.mjs exchanged one-time CLI auth codes with a check-then-act sequence:

const row = await db.execute({ sql: "SELECT ... used FROM cli_auth_codes WHERE code = ?" });
if (row.used) return 400;
...
await db.execute({ sql: "UPDATE cli_auth_codes SET used = 1 WHERE code = ?" });

Against a remote libsql/Turso database (the production configuration uses DATABASE_AUTH_TOKEN, i.e. DB over the network), two concurrent exchanges of the same code can both pass the used = 0 check before either UPDATE lands — minting two API keys from a single one-time code (TOCTOU race).

Fix

Claim the code atomically and reject the exchange when the claim affects zero rows:

UPDATE cli_auth_codes SET used = 1 WHERE code = ? AND used = 0

If rowsAffected === 0, the code was already claimed (or doesn't exist) → 400. Correct on both local file: DBs and remote Turso.

Tests

New integration tests in apps/pwa/test/cli-token.test.mjs exercise the real router:

  • single-use enforced: second exchange of the same code → 400,
  • a failed PKCE verification does not consume the code (can be retried with the right verifier),
  • expired codes are rejected.

Note: with a local file: DB the libsql client serializes statements, so the race window does not reproduce locally — the test asserts the guarantee (single-use), which holds in both deployments; the fix removes the window that exists with a remote DB.

The exchange did SELECT used -> UPDATE used=1 as separate statements. With a
remote (network) libsql database, two concurrent exchanges can both pass the
`used` check before either UPDATE lands, so one single-use code minted two
API keys. Claim with a single conditional UPDATE ... WHERE used = 0 and
reject when rowsAffected is 0. Adds integration tests for the single-use,
PKCE, and expiry contracts (skip cleanly when apps/pwa deps aren't installed).
@ralyodio
ralyodio merged commit 604121f into moshcoder:main Jul 26, 2026
3 checks passed
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.

2 participants