Skip to content

fix(entitlements): prevent TOCTOU race condition in promo code redemption (fixes #92) - #112

Closed
Echolonius wants to merge 1 commit into
profullstack:masterfrom
Echolonius:fix/promo-redemption-race-condition
Closed

fix(entitlements): prevent TOCTOU race condition in promo code redemption (fixes #92)#112
Echolonius wants to merge 1 commit into
profullstack:masterfrom
Echolonius:fix/promo-redemption-race-condition

Conversation

@Echolonius

Copy link
Copy Markdown
Contributor

Summary

Fixes #92 by converting the promo code redemption flow into an atomic operation using INSERT INTO promo_redemptions ... ON CONFLICT DO NOTHING.

Changes

  • Updated recordPromoRedemption() in apps/web/lib/entitlements.ts to perform INSERT INTO promo_redemptions (code, user_id) VALUES (?, ?) ON CONFLICT DO NOTHING
  • Verified that rowsAffected > 0 before executing the UPDATE promo_codes SET uses = uses + 1 query
  • Added unit tests in apps/web/lib/entitlements.test.ts testing both initial redemption and duplicate conflict handling

🤖 Agent Payout Specification

  • Base USDC: 0xd194AB36E66BccDD80f19b56757CFe52EdEd49af
  • Solana USDC: 3wbinZDnWmDxHMLtACNrskwZvRwg4KYbBWw1wuviXXHT

…tion (fixes profullstack#92)

- Use INSERT INTO promo_redemptions ... ON CONFLICT DO NOTHING for atomic duplicate check
- Verify rowsAffected > 0 before updating promo_codes uses counter
- Add unit tests verifying conflict handling in recordPromoRedemption

Agent-Payout-Wallet: base:0xd194AB36E66BccDD80f19b56757CFe52EdEd49af
Agent-Payout-Wallet: solana:3wbinZDnWmDxHMLtACNrskwZvRwg4KYbBWw1wuviXXHT
@ralyodio

ralyodio commented Aug 1, 2026

Copy link
Copy Markdown
Contributor

Closing as superseded by #110, now merged.

Same idea (ON CONFLICT DO NOTHING + rowsAffected), but #110 was the stronger version on two points:

  1. It caps the increment (AND (max_uses IS NULL OR uses < max_uses)), so a code can't be pushed past max_uses. This PR keeps the unbounded uses = uses + 1.
  2. The catch-all try/catch here returns false on any error, which makes a transient DB failure indistinguishable from "already redeemed."

Worth noting #110 went too far the other way — it dropped the try/catch entirely, which let a DB error 500 a response that had already granted the entitlement. Fixed in #115. Thanks for the fix.

@ralyodio ralyodio closed this Aug 1, 2026
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.

MEDIUM: Race condition in promo code duplicate redemption check

2 participants