All vulnerabilities
HighA04:2021CWE-362Business Logic

Race Condition (TOCTOU)

Race conditions occur when multiple concurrent requests exploit a Time-of-Check to Time-of-Use window — allowing attackers to apply promo codes multiple times, double-spend balances, or claim rewards that should only be redeemable once.

What Is a Race Condition?

A race condition (TOCTOU — Time-of-Check to Time-of-Use) occurs when an application checks a condition, then acts on it, but doesn't prevent concurrent requests from exploiting the window between the check and the action.

In web applications, this typically means firing many simultaneous requests at an endpoint that should only succeed once — before the server has time to mark the resource as used.

How It Works

Consider a promo code redemption endpoint:

# Vulnerable implementation
def redeem_promo(code: str, user_id: str):
    promo = db.get_promo(code)          # 1. CHECK — is it valid?
    if promo.used:
        return error("Already used")
    apply_discount(user_id, promo)      # 2. USE — apply the discount
    db.mark_used(promo.id)              # 3. MARK — too late, race already won

An attacker sends 15 simultaneous requests at exactly the same millisecond:

# All 15 fire concurrently before any single one completes step 3
for i in $(seq 1 15); do
  curl -X POST /api/redeem -d '{"code":"SAVE50"}' &
done

Multiple requests pass step 1 before any completes step 3 — the discount is applied many times.

Common Targets

  • Promo / coupon codes — single-use codes applied multiple times
  • Gift card balance — spending the same balance before the deduction commits
  • Referral bonuses — claiming a bonus for each concurrent signup
  • Rate limits — bypassing "one free trial" or "one download" limits
  • Inventory — purchasing the last item multiple times simultaneously

Real-World Impact

  • Revenue loss — promo codes applied 10–100× instead of once
  • Free tier bypass — multiple concurrent trial signups before rate limit kicks in
  • Double-spend — cryptocurrency or credit balance exploited concurrently
  • Inventory oversell — selling more items than in stock

How to Fix

Use atomic database operations — the gold standard:

-- Atomic claim: only one request can succeed
UPDATE promo_codes
SET used_by = $1, used_at = NOW()
WHERE code = $2 AND used_by IS NULL
RETURNING id;
-- If 0 rows returned, promo was already claimed

Use database-level locks:

# PostgreSQL advisory lock — only one process proceeds at a time
with db.transaction():
    db.execute("SELECT pg_advisory_xact_lock(hashtext($1))", [promo_code])
    promo = db.get_promo(promo_code)
    if promo.used:
        raise ValueError("Already used")
    # safe to proceed

Use Redis atomic operations for high-throughput scenarios:

# SETNX — set if not exists — atomic in Redis
result = redis.setnx(f"promo:{code}:lock", user_id)
if not result:
    return error("Already claimed")
redis.expire(f"promo:{code}:lock", 60)  # TTL safety net

Idempotency keys for payment APIs:

# Stripe / payment processor — idempotency prevents double-charge
stripe.PaymentIntent.create(
    amount=1000,
    idempotency_key=f"order-{order_id}"  # same key = same result
)

What VibeWShield Detects

VibeWShield's Race Condition scanner identifies endpoints containing keywords: redeem, coupon, promo, checkout, purchase, transfer, withdraw, claim, subscribe, invite, bonus, reward. It then fires 15 simultaneous requests via asyncio.gather().

If ≥30% of requests return HTTP 200/201 (more than one success when only one should be allowed), a race condition is flagged as High severity with the request count and success rate as evidence.

#race-condition#toctou#concurrency#promo-code#payment

Free security scan

Test your app for Race Condition (TOCTOU)

VibeWShield automatically checks for Race Condition (TOCTOU) and 40+ other vulnerabilities using 63 scanners — in under 3 minutes, no signup required.

Scan your app free