server: one API key per device — schema-level guarantee#282
Conversation
Add unique index on api_keys.device_id. Without this, a regression in the /v1/auth/exchange redemption flow (which does DELETE + INSERT on api_keys keyed by device_id) could leave two rows for the same device — the exact corrupted state a recent audit identified as reachable via concurrent setup-token redemptions. With the index in place, the second INSERT violates the constraint and fails loudly instead of silently producing two long-lived API keys. No defensive cleanup in the migration: if rows violating the constraint exist, that is itself a signal worth investigating, not silently fixing. Auto-deleting "older" rows could just as easily revoke the legitimate user's key as the attacker's, depending on which redemption arrived first. api_keys is small (~one row per active device), so a non-CONCURRENT CREATE UNIQUE INDEX completes in milliseconds. Refresh docs/generated/db-schema.md to reflect the new index. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
Warning Rate limit exceeded
To keep reviews running without waiting, you can enable usage-based add-on for your organization. This allows additional reviews beyond the hourly cap. Account admins can enable it under billing. ⌛ How to resolve this issue?After the wait time has elapsed, a review can be triggered using the We recommend that you space out your commits to avoid hitting the rate limit. 🚦 How do rate limits work?CodeRabbit enforces hourly rate limits for each developer per organization. Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout. Please see our FAQ for further information. ℹ️ Review info⚙️ Run configurationConfiguration used: Organization UI Review profile: CHILL Plan: Pro Run ID: ⛔ Files ignored due to path filters (1)
📒 Files selected for processing (4)
✨ Finishing Touches🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Review rate limit: 0/1 reviews remaining, refill in 53 minutes and 27 seconds.Comment |
## Summary - Combines the security fixes from #280, #281, and #282 on top of `g/consolidate-server-boundaries` so those separate PRs can be closed after review. - Hashes setup tokens at rest while still returning the raw token once for pairing. - Claims setup tokens atomically during `/v1/auth/exchange` so concurrent redemption cannot mint multiple API keys. - Adds a unique index on `api_keys(device_id)` as the schema-level one-active-key-per-device guarantee. - Updates tests for the new invariant and adds an assertion that raw setup tokens are not stored in the database. ## Validation - `bun run gen:db-schema:check` - `bun run typecheck` - `bun test test/auth-resolvers.test.ts` - `bun run test` — 296 pass, 0 fail ## Notes This PR targets `g/consolidate-server-boundaries` intentionally. Once it lands into the consolidation branch, the older security PRs #280, #281, and #282 should be redundant. <!-- CURSOR_SUMMARY --> --- > [!NOTE] > **Medium Risk** > Changes security-sensitive auth flows and adds a data-cleanup migration with a new uniqueness constraint on `api_keys`, which could affect existing tokens/devices if assumptions are wrong. > > **Overview** > **Hardens the setup-token pairing flow** by storing only a SHA-256 hash of setup tokens and updating `/v1/auth/exchange` to *atomically* claim (redeem) tokens while also checking expiry. > > **Enforces one active API key per device** by adding a unique index on `api_keys.device_id` (with a migration that deletes older duplicate keys per device) and updating tests/docs to match the new hashing and uniqueness invariants. > > <sup>Reviewed by [Cursor Bugbot](https://cursor.com/bugbot) for commit 6cede0e. Bugbot is set up for automated code reviews on this repo. Configure [here](https://www.cursor.com/dashboard/bugbot).</sup> <!-- /CURSOR_SUMMARY --> <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit * **Bug Fixes** * Fixed race condition in setup token redemption that could create duplicate API keys * Enforced one-API-key-per-device constraint * **Security** * Setup tokens are now securely hashed before storage <!-- end of auto-generated comment: release notes by coderabbit.ai --> --------- Co-authored-by: Erik Olsson <valross2@gmail.com> Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
closing due to #289 |
Summary
UNIQUE INDEX api_keys_device_uniqueonapi_keys(device_id)./v1/auth/exchangedoesDELETE FROM api_keys WHERE device_id=? → INSERT api_keys (...)to enforce "one active key per device". A recent audit showed that a concurrency bug in setup-token redemption could let two requests interleave that delete/insert pair and leave two validapi_keysrows for the same device — each caller walking away with a working API key.23505(unique violation) instead of producing a phantom key.Migration
CREATE UNIQUE INDEX api_keys_device_unique ON api_keys USING btree (device_id);api_keysis ~1 row per active device, so the ACCESS EXCLUSIVE lock completes in milliseconds and CONCURRENTLY would force the index out of Drizzle's transaction.Hot path impact
keyHash(separate unique index, unchanged). New index does not affect the hot path.DELETE … WHERE device_id=?in both/v1/auth/exchange(auth/github.ts) andDELETE /api/me/devices/:id(user/routes.ts). Net positive.Audit context
PR 3 of 3 from the desktop pairing security review. Independent of PRs #280 and #281 — based on
main, no merge order required, but this lands cleanly only if no production rows currently violate the constraint (expected case).Test plan
bun run db:generate— produced0016_dazzling_victor_mancha.sqlbun run gen:db-schema— refresheddocs/generated/db-schema.mdbun run typecheck(apps/server) — cleanbun run test(apps/server) — 290 pass / 0 fail (test bootstrap runs the new migration)SELECT device_id, COUNT(*) FROM api_keys GROUP BY 1 HAVING COUNT(*) > 1returns zero rows in production🤖 Generated with Claude Code
Note
Medium Risk
Adds a new unique DB constraint on
api_keys.device_id, which can cause the migration to fail if production already contains duplicates and will turn concurrent redemptions into23505errors instead of creating multiple valid keys.Overview
Adds a schema-level guarantee that each device can have at most one active API key by introducing a new unique index
api_keys_device_uniqueonapi_keys(device_id).Updates the Drizzle schema/migration metadata and regenerates the DB schema docs so the new uniqueness constraint is reflected across code and documentation.
Reviewed by Cursor Bugbot for commit cbc89b3. Bugbot is set up for automated code reviews on this repo. Configure here.