Build1 publisher3 min readPublished
The balance check that runs before the debit is CWE-367, not a guardrail
A dev.to piece maps validate-then-commit payment flows onto a race condition class catalogued in 2006. The useful part is not the taxonomy but where the check has to live.
The Engineer · Build desk
Drafted by a language model from the sources cited here and checked against its claim ledger before publication. How we use AISend a correction

What happened
- Time-of-check to time-of-use (TOCTOU) is a formally catalogued class of race condition, CWE-367, first documented in 2006 and described as still actively relevant two decades later.
- The CWE-367 pattern: a program evaluates a precondition, then acts on a resource whose state it assumes is unchanged, without any atomicity guarantee between the two operations.
- access() and fopen() are two independent system calls; nothing forces the kernel to guarantee that the file referenced at access() time is the same file referenced at fopen() time.
- An attacker with write access to the containing directory can swap the target for a symlink to /etc/shadow inside the race window, and the check will have been technically correct at the moment it ran.
- Python has the identical pattern (os.path.exists(path) followed by open(path)), and it is common enough that Semgrep ships a default rule for it.
Compiled by The EngineerSomething wrong?How this is made
Why it matters
A dev.to post by Oludele Oluwapelumi files validate-then-commit payment flows under the oldest entry in the race-condition catalogue: CWE-367, time-of-check to time-of-use, first documented in 2006 and, in the author's phrasing, still actively relevant two decades later [1]. If the mapping holds, the remediation is not a stricter validator but a different transaction boundary, and most teams running a "we validate first" architecture have never measured the gap between the two.
The pattern is defined narrowly: a program evaluates a precondition, then acts on a resource whose state it assumes is unchanged, with no atomicity guarantee between the two operations [2]. In the canonical C version, access() and fopen() are independent system calls, and nothing forces the kernel to guarantee that the file named at check time is the file opened at use time [3]. An attacker with write access to the containing directory swaps the target for a symlink to /etc/shadow inside the window, and the check will have been technically correct at the moment it ran [4]. Python reproduces the shape with os.path.exists() followed by open(), commonly enough that Semgrep ships a default rule for it [5].
The failure mode is worth stating precisely, because it is the part that transfers. Both the check and the use are individually correct; what is wrong is the system's belief about what the resource refers to, and it is wrong specifically because time passed and something else held write access during that interval [8]. Timing, not logic, is the vulnerability [8]. A balance read is a check. A debit is a use. Anything that can write the same row between them is the attacker in the diagram, including your own retry logic.
The prescribed fixes are all structural, not defensive: collapse check and use into one atomic operation, use O_CREAT with O_EXCL, call fstat() on an already-open descriptor instead of stat() on a path, or hold a lock such as flock() or fcntl(F_SETLK) across both steps [6]. The stated principle is never to trust a decision made against a resource you no longer have exclusive reference to [7]. Read across to a ledger, that is a conditional update, a row lock held from read to write, or serializable isolation, which is the same thing as saying the check belongs inside the transaction that mutates the balance.
Nobody should treat this as solved by seniority. CVE-2019-5736 was a TOCTOU race in runc that let a malicious container overwrite the host runc binary and escape [9], and CVE-2016-9806 was a race in Linux kernel netlink handling that enabled privilege escalation [10]; both landed a decade after CWE-367 was defined, in code reviewed far harder than most application code ever is [11].
The second lens is older than the CWE. Commitment ordering has been formalized in distributed database theory since 1990, with Yoav Raz's VLDB 1992 paper [12], which requires that the chronological order of commit events be compatible with the precedence order of conflicting operations, and holds that per-transaction correctness in isolation is not sufficient [13]. It was built for global serializability across autonomous resource managers with no shared clock, no shared concurrency control and no assumed coordination [14], which is a fair description of a payment stack assembled from a wallet service, a processor and a ledger. To the author's credit, the piece declines to assert that the specific failure it discusses meets the formal conditions of a commitment ordering violation, saying that would need proof it does not attempt [15].
What to watch is your own instrumentation. The material offers no payment-sector CVE and no measured window; its exploited examples are a container runtime and a kernel subsystem [16]. So the number to produce is local: elapsed time at p99 between the read that authorizes and the write that commits, and whether any lock or predicate spans them. If nothing does, the window exists whether or not anyone has found it.