Security

    TOCTOU and race conditions in mobile apps

    A 2026 view of a TOCTOU race where two concurrent payment requests both pass a balance check before either debits, contrasted with an atomic transactional check-and-debit

    A race condition is a bug of timing, and a security-relevant one often takes the form of TOCTOU: time-of-check to time-of-use. You check a condition, a permission, a balance, a file, and then act on it a moment later, and if the state can change in between, an attacker can exploit that gap. On mobile this shows up as double-submitted payments, concurrent requests slipping past a check, or a file swapped between validation and use. The fix is to make the check and the action atomic, and to enforce the critical decision on the server. Here is what TOCTOU and race conditions are, where they show up, and how to prevent them.

    Short answer

    A TOCTOU (time-of-check to time-of-use) bug is a race condition where a security check is performed at one moment and the checked resource is used later, and the state changes in between, letting an attacker exploit the gap. Per OWASP, examples on mobile include double-submitted requests racing a server-side check, concurrent operations slipping past a one-time check, or a resource swapped between validation and use. The fix is to make the check and the use atomic, enforce critical decisions on the server with transactional logic or locking, use idempotency for operations that must happen once, and not rely on a client-side check that can be invalidated before the action. Do not assume state checked earlier still holds when you act.

    What you should know

    • TOCTOU is a timing gap: state changes between the check and the use.
    • Race conditions exploit concurrency: simultaneous operations slip past checks.
    • Client-side checks are racy: and bypassable; enforce server-side.
    • Make check and use atomic: so nothing changes in between.
    • Use idempotency for once-only actions: like payments.

    What is a TOCTOU or race condition?

    It is a flaw where the correctness of an operation depends on timing that an attacker can influence. In a TOCTOU bug specifically, your code checks a condition, that a user is authorized, that a balance is sufficient, that a file is safe, and then performs an action based on that check a moment later, and if the state can change between the check and the use, the action proceeds on a stale assumption. A race condition more broadly is when concurrent operations interleave in a way that breaks an assumption, such as two requests both passing a one-time check before either completes. On mobile, these often span the client and server: the client submits, the server checks and acts, and if checks and updates are not atomic, simultaneous or replayed requests can slip through. The unifying issue is trusting that a checked state still holds when you act on it.

    Where does it show up in mobile apps?

    In gaps between checking and acting, especially across client and server. The table lists common cases.

    ScenarioThe race
    Double-submitted paymentTwo requests both pass the balance or one-time check
    Concurrent redemptionA coupon or reward claimed more times than allowed
    File checked then usedA file swapped between validation and use
    Client check then server actionThe client's check is stale or bypassed by the time the server acts
    Auth state checked then trustedState changes between the check and the protected action

    The recurring pattern is a one-time or conditional action that is checked and then performed non-atomically, so duplicate or concurrent attempts can both succeed. Payment and redemption flows are classic, where a user submitting the same request twice quickly, or many times concurrently, can get double value if the server checks and debits in separate, non-atomic steps. The file case is the textbook TOCTOU, where what is checked is not what is ultimately used.

    How do you prevent it?

    Make the critical check-and-act atomic, and enforce it on the server. The core fix is to ensure the check and the action that depends on it happen as one indivisible operation, so nothing can change in between, typically through a database transaction, a lock, or an atomic compare-and-set on the server rather than a check followed by a separate update. For once-only operations like payments, use an idempotency key so a duplicate or replayed request is recognized and not processed twice. Enforce these decisions on the server, since a client-side check is racy and bypassable, and the client cannot guarantee atomicity against an attacker controlling the requests. Do not act on a previously checked state without revalidating it atomically at the point of use, and for files, operate on the same handle you validated rather than re-resolving a path that could have changed. The principle is to remove the gap between check and use where it matters.

    What to watch out for

    The first trap is checking a condition on the client and trusting it when the server acts, since the client check is racy and bypassable; enforce server-side. The second is a payment or redemption flow that checks and updates non-atomically, letting duplicate or concurrent requests double-spend; use transactions and idempotency. The third is validating a file then using a path that could have changed. Race conditions are logic and concurrency flaws fixed on your server and in your code, so a pre-submission scan such as PTKD.com (https://ptkd.com), which reads the binary against OWASP MASVS, surfaces the app's endpoints and behavior but the atomic enforcement is yours to implement. Server-side transactional logic is the fix.

    What to take away

    • A TOCTOU bug is a race condition where state changes between a security check and the use of the checked resource, which an attacker can exploit.
    • On mobile it shows up as double-submitted payments, concurrent redemptions, swapped files, or stale client checks the server trusts.
    • Make the check and the action atomic with transactions, locks, or compare-and-set, enforce it server-side, and use idempotency for once-only operations.
    • Race conditions are fixed in your server and code; use a pre-submission scan such as PTKD.com to understand the app's endpoints while you implement atomic enforcement.
    • #toctou
    • #race-condition
    • #concurrency
    • #idempotency
    • #api-security
    • #app-security
    • #mobile

    Frequently asked questions

    What is a TOCTOU bug?
    TOCTOU, time-of-check to time-of-use, is a race condition where your code checks a condition, like authorization, a balance, or a file's safety, and then acts on it a moment later, and the state changes between the check and the use, so the action proceeds on a stale assumption. An attacker who can influence the timing exploits that gap. The textbook case is checking a file then using a path that was swapped in between, but it generalizes to any checked-then-used resource.
    Where do race conditions show up in mobile apps?
    In gaps between checking and acting, especially across client and server. Common cases are a double-submitted payment where two requests both pass a balance or one-time check, a coupon or reward redeemed more times than allowed through concurrent requests, a file swapped between validation and use, and a client-side check that is stale or bypassed by the time the server acts. The pattern is a one-time or conditional action checked and then performed non-atomically.
    How do I prevent a double-spend race?
    Make the check and the action atomic and enforce it on the server. Rather than checking the balance and then debiting in separate steps, use a database transaction, a lock, or an atomic compare-and-set so the check and update happen as one indivisible operation, and use an idempotency key so a duplicate or replayed request is recognized and not processed twice. A client-side check cannot prevent this, since an attacker controls the requests, so the atomic enforcement must be server-side.
    Why are client-side checks racy?
    Because the client cannot guarantee that a state it checked still holds when the server acts, and an attacker controlling the requests can send duplicates or concurrent calls that interleave around the check. A client check is also bypassable entirely, since the attacker can call the API directly. So a check on the client is at best a user-experience aid; the security-relevant check must happen atomically on the server at the point of use, not be trusted from the client.
    Can a scan find race conditions?
    Race conditions are logic and concurrency flaws in your server and code, so they are fixed and tested there rather than found by inspecting the app binary. A pre-submission scan such as PTKD.com reads the binary against OWASP MASVS and surfaces the app's endpoints and behavior, which helps you understand the surface, but the atomic, transactional enforcement that prevents TOCTOU and double-spends is something you implement and test on the server, with idempotency for once-only operations.

    Keep reading

    Scan your app in minutes

    Upload an APK, AAB, or IPA. PTKD returns an OWASP-aligned report with copy-paste fixes.

    Try PTKD free