# Non-atomic check-then-act races

Separate read and write on balances, limits, tokens or single-use state; test with parallel requests

_Area: Web business logic and race conditions. Seen in 7 disclosed reports; 2 confirmed bounties: $5,000 and $15,250; latest 2025-05-09. Updated 2026-10-23._

## How it works
A decision is made from stored state (balance sufficient, coupon unused, transaction not yet redeemed, email unverified) and the state is changed in a later, separate step. Concurrent requests all read the old state, all pass the check, and all proceed to act. The assumption that "check and act are one" breaks whenever they are not in a single transaction, row lock, conditional write, or unique constraint. The result is duplicated single-use actions, overdrawn balances, or a trust anchor (a verified email) applied to the wrong object.

## Where it shows up
- Read-then-write on shared state: balance debit, credit cap, invite quota, coupon redemption, faucet payout [E2](https://bugbountylibrary.com/r/blog%3Ajosipfranjkovic-com-blog-race-conditions-on-web.md)[E3](https://bugbountylibrary.com/r/h1%3A1438052.md)
- Email change and email confirmation as separate operations; token validated against current rather than captured email value [E1](https://bugbountylibrary.com/r/h1%3A300305.md)
- Auto-linking accounts by matching email, where verification is the only trust anchor [E1](https://bugbountylibrary.com/r/h1%3A300305.md)
- Entitlement/claim endpoints lacking idempotency, where a per-request unique field (e.g. `meta`) defeats dedup [E4](https://bugbountylibrary.com/r/h1%3A2616045.md)
- Mobile IAP verification keyed on a transaction_id/receipt that is checked then credited non-atomically [E5](https://bugbountylibrary.com/r/h1%3A801743.md)
- Shared in-memory maps mutated without concurrency control (Go maps) [E3](https://bugbountylibrary.com/r/h1%3A1438052.md)

## How to review
Find every endpoint where a check and a mutation touch the same resource. The invariant: the check and the write must be atomic with respect to concurrent requests. Verify:
- Is the read and write one transaction/lock, or two round-trips?
- Does the write re-assert the condition, or trust the earlier read? Idioms: conditional update (`UPDATE ... WHERE balance >= x`), `SELECT ... FOR UPDATE`, unique index on transaction_id/receipt, idempotency key.
- Email flows: is the token bound to the exact email value at issue time, and are change+confirm one transaction? [E1](https://bugbountylibrary.com/r/h1%3A300305.md)
- Claim/entitlement endpoints: is there a unique constraint or idempotency key on the claim, not just a pre-check? [E4](https://bugbountylibrary.com/r/h1%3A2616045.md)
- Are shared maps guarded by a mutex or replaced with safe structures? [E3](https://bugbountylibrary.com/r/h1%3A1438052.md)
- Detection limit: static reading rarely proves a race; confirm by firing parallel requests and comparing final state to the configured limit.

## Variants seen
- Email-change/verification TOCTOU enabling account takeover via auto-link [E1](https://bugbountylibrary.com/r/h1%3A300305.md)
- Concurrent duplication of single-use actions: confirmation codes, withdrawals, purchases, invites [E2](https://bugbountylibrary.com/r/blog%3Ajosipfranjkovic-com-blog-race-conditions-on-web.md)
- Parallel faucet requests bypassing a per-address cap through non-atomic balance check and send [E3](https://bugbountylibrary.com/r/h1%3A1438052.md)
- Free-entitlement claim endpoint lacking idempotency, yielding multiple free domains [E4](https://bugbountylibrary.com/r/h1%3A2616045.md)
- Replayed IAP transaction_id credited multiple times under concurrency [E5](https://bugbountylibrary.com/r/h1%3A801743.md)

## Impact and severity
Ranged from duplicated single-use actions [E2](https://bugbountylibrary.com/r/blog%3Ajosipfranjkovic-com-blog-race-conditions-on-web.md) to full store takeover [E1](https://bugbountylibrary.com/r/h1%3A300305.md), draining more tokens than the configured cap [E3](https://bugbountylibrary.com/r/h1%3A1438052.md), multiple free entitlements [E4](https://bugbountylibrary.com/r/h1%3A2616045.md), and inflated coin balances [E5](https://bugbountylibrary.com/r/h1%3A801743.md). Severity rises when the raced resource is an authentication trust anchor or a privileged entitlement, and when the endpoint is unauthenticated. Bounties in this set ranged $5,000–$15,250, consistent with takeover- and entitlement-class outcomes.

## Evidence
- E1: https://bugbountylibrary.com/r/h1%3A300305.md
- E2: https://bugbountylibrary.com/r/blog%3Ajosipfranjkovic-com-blog-race-conditions-on-web.md
- E3: https://bugbountylibrary.com/r/h1%3A1438052.md
- E4: https://bugbountylibrary.com/r/h1%3A2616045.md
- E5: https://bugbountylibrary.com/r/h1%3A801743.md
