`shuvonsec/race-condition-tester`: The Small Python Script That Makes Threads Hit All at Once

A zero-dependency race-condition tester for bug bounty work, built around one idea that matters: synchronize every request, then let the server blink first.

7 min read • View on GitHub • More from shuvonsec

A wide racetrack scene with several runners frozen behind a spring-loaded gate, then released together toward a server-shaped finish line. It visualizes the repo's core trick: synchronization turns ordinary threads into a deliberate race attack.
The repository's edge is not raw speed. It is controlled simultaneity.
Key Takeaways

Most race-condition scripts spray requests and hope timing lines up. This repo does something sharper: it holds every worker at a barrier, then releases them together so the server has to answer a synchronized rush. That is the difference between noise and a timing primitive.

The release valve, not the flood

The barrier is the whole trick. Every thread gets ready first, then blocks until the last one arrives, and only then do they all move. The point is not to create more traffic. The point is to compress arrival time so the first write, the first redemption, or the first failed login lands before the server has updated its own state.

Why ordinary threads miss the window

Plain threading is messy. Thread startup, scheduler jitter, network latency, and interpreter overhead smear request timing across a wider window than you want. If you are testing a race, that smear is the enemy, because the server only has to recover between arrivals to make the bug disappear.

Barrier-based testing works because the last thread to arrive controls the instant every request leaves.

That is why the barrier matters more than the thread count. A dozen badly timed requests are weaker than four perfectly aligned ones. The script is trying to create a thundering herd on purpose, which is exactly what most web defenses are least prepared to reason about.

How the script is wired

import json
import threading
import urllib.error
import urllib.request

barrier = threading.Barrier(worker_count)

def post(url, payload):
    data = json.dumps(payload).encode('utf-8')
    req = urllib.request.Request(url, data=data, headers={'Content-Type': 'application/json'})
    try:
        with urllib.request.urlopen(req, timeout=10) as resp:
            return resp.status, resp.read().decode('utf-8', 'replace')
    except urllib.error.HTTPError as exc:
        return exc.code, exc.read().decode('utf-8', 'replace')

def worker(payload):
    barrier.wait()
    return post(target_url, payload)

The standard library choice is not a compromise. It is a deployment strategy. By leaning on `urllib.request`, `threading`, and `urllib.error.HTTPError`, the tool can run on a machine with no `pip install` step, which is exactly what you want on a jump box, a VPS, or any environment where speed matters more than polish.

What it targets

The interesting targets here are not just classic low-level races. The repo is pointed at 2FA rate limits, duplicate actions, coupon-style mutations, negative amounts, and other business-logic failures where the first request should win and the rest should be rejected. GraphQL is especially useful because it often compresses the check and the action into one mutation, which can widen the gap between intention and actual state.

ToolMain goalSynchronization methodBest atTrade-off
race-condition-testerForce simultaneous HTTP requests against web targetsPython threading.BarrierLightweight bug bounty tests and portable one-off runsNarrow scope and less throughput than larger purpose-built suites
TREMExploit web timing windows with higher precisionNetwork-level request orchestrationPrecision race exploitation on modern HTTP servicesMore specialized to learn and operate
FrayExpose concurrency bugs in Java code by steering executionScheduler control and replayDeterministic bug finding in tests and production codeNot a web request attack tool
ThreadSanitizerDetect shared-memory data races during developmentRuntime instrumentationC, C++, and Go debugging workflowsFinds code races, not request-level exploit windows
A split close-up of request envelopes entering a funnel one by one on the left, and as a tightly packed bundle on the right. The image shows why staggered traffic loses the race window while aligned traffic can slip through before state updates land.
Timing beats volume when the server only updates state after the first request lands.

Seen next to broader tools, the repo lands in a very specific lane. It is small, surgical, and easy to carry. That makes it useful when the job is not to build a framework, but to prove whether one timing window exists on one endpoint, right now.

Why the zero-dependency choice matters

A bigger tool can be better in the abstract and worse in the field. If you need a browser, a proxy stack, or a heavy runtime just to start testing, you have already made the workflow harder than it needs to be. This repo keeps the surface area tiny, and that is an advantage when the real problem is finding a narrow window before the server state catches up.

The larger lesson

Race conditions are not only a CPU problem. Any system that checks, then acts, then updates state can be beaten by a coordinated observer who arrives faster than the state machine heals. That is why payments, rate limits, coupon redemption, and multi-step account flows deserve the same suspicion as low-level locks.