💬 Intro
Most developers think of timing leaks as a cryptographic concern. But the truth is: even a simple if-else in your application logic can expose private data — just through time.
This post walks through how a common feature like “Forgot Password” can silently leak whether an email exists, and how to fix it. Along the way, we’ll uncover how synchronous logic, conditional branching, and response delays can betray your backend logic — and how to defend against it.
🐣 1. The Naive Implementation

Seems safe, right?
We never tell the user whether the email exists.
But the problem isn’t what we return — it’s what we do.
🕵️ 2. Timing Leaks in Action
Even if the attacker can’t see your database or error logs, they can simply measure time.
🔓 The Leak
When the email exists:
- You send an email (which may take longer)
When it doesn’t:
- You skip that logic
An attacker can run:
time curl -X POST -d "email=real@example.com" https://example.com/forgot-password
…and compare timings between real@example.com
and fake@example.com
.
📉 Real-World Difference
Even 5–10 ms delay can be measured — and automated over thousands of requests.
This means your app is revealing:
- Who has an account
- Who doesn’t
That’s already enough for enumeration, targeted phishing, and more.
🛠️ 3. Trying to Queue It
A common fix is to queue the email logic:
if ($user) {
dispatch(new SendResetEmailJob($user));
}
This removes the mail delay — but not the lookup difference.
User::where('email')
still costs time if the user exists vs doesn’t. And this difference can still be observed.
🧠 4. Normalizing the Timing
To make the response time constant, we need to simulate the same workload whether the user exists or not.
Here’s a more secure approach:

✅ Now:
- Every request takes roughly the same time
- Real users still get emails
- Attackers can’t easily measure differences
📈 Bonus: Even Better with Fake Work
To make it even more convincing, perform some actual fake work if the user doesn’t exist:
if (! $user) {
// Simulate hashing workload
hash('sha256', $request->email . now());
}
This better mimics the real logic path and adds noise to timing attacks.
🔐 Final Thought
You don’t need a crypto library to leak secrets.
Sometimes, your logic is enough.
If two different conditions take two different paths, you’re probably leaking timing.
Fixing it doesn’t mean slowing down your app — it means leveling the field.
🕰️ Code like someone is measuring your clock. Because they are.