← Back to list
● Based on the 2013-02 prepaid-card processor hack (two waves, $45M combined)

A structure where 10 simultaneous requests all go through —
I'm publishing the code that stops it

In 2013, hackers exploited the gap between balance-check and balance-deduction (a TOCTOU race condition) in a prepaid-card processor's withdrawal logic, draining $45M by cashing out simultaneously from ATMs in 27 countries. I reproduced the same bug class and verified, by firing 10 concurrent withdrawal requests, that an atomic lock stops it.

See live demo → Jump to code

Who this is for

A learning resource for backend developers who've written check-then-act logic against a shared resource like a balance — the gap between those two steps is exactly the vulnerability.

$45M
Combined loss across the 2 waves of the 2013 incident
10/10
Concurrent withdrawals that succeeded against the vulnerable side during testing
5/10
Concurrent withdrawals that succeeded against the guarded side (exactly up to the balance limit)

Code — race-condition-withdrawal.js

A vulnerable withdrawal API with a gap between the balance check and the deduction (TOCTOU).

race-condition-withdrawal.js
// Vulnerable withdrawal API — modeled on the 2013 prepaid-card
// processor hack. The balance CHECK and the deduction ACT are two
// separate steps; the gap between them (a realistic DB round-trip)
// is a race window where concurrent requests all see the balance
// from BEFORE any of them has written back.
class RaceConditionWithdrawal {
  constructor(account) {
    this.account = account; // { balance }
  }

  async withdraw(amount) {
    const current = this.account.balance; // CHECK
    await simulatedDbRoundTrip(); // the race window
    if (current < amount) return { status: 'insufficient_funds' };
    this.account.balance = current - amount; // ACT — based on a stale read
    return { status: 'ok', newBalance: this.account.balance };
  }
}

module.exports = { RaceConditionWithdrawal };

Code — atomic-withdrawal-guard.js

The fix: the check and the deduction happen as one atomic, locked operation.

atomic-withdrawal-guard.js
// The fix: no other request can act between the check and the
// deduction — an account-level lock makes both one atomic step.
// (A real system would use a DB row lock or an atomic
// UPDATE ... WHERE balance >= amount.)
class AtomicWithdrawalGuard {
  constructor(account) {
    this.account = account;
    this.locked = false;
  }

  async withdraw(amount) {
    while (this.locked) await sleep(5);
    this.locked = true;
    try {
      await simulatedDbRoundTrip(); // same delay, now INSIDE the lock
      if (this.account.balance < amount) return { status: 'insufficient_funds' };
      this.account.balance -= amount;
      return { status: 'ok', newBalance: this.account.balance };
    } finally {
      this.locked = false;
    }
  }
}

module.exports = { AtomicWithdrawalGuard };

Price

Free (for now)
A deeper version (distributed locks, multi-account transactions) ships later — pay-what-you-want, starting at $0.
goalsgo7574@gmail.com
This isn’t a purchase — just email me "notify me about the paid version" and I’ll reach out when it ships.
Note: This is a scaled-down learning implementation. The lock is in-memory within a single process — a real environment with multiple server instances needs a distributed lock.

Leave feedback

Questions or feedback about this product? Leave your email if you'd like a reply (optional).