An orders-create endpoint. POST. No idempotency key. Identical to the payment-create bug in cluster 3 — different surface, same issue. Mesrai flagged it.
The vulnerable diff
// api/orders.ts
ordersRouter.post("/orders", requireAuth, async (req, res) => {
// BUG: no idempotency. Network retry creates two orders.
const order = await db.orders.create({
data: { userId: req.user.id, items: req.body.items, total: req.body.total },
});
return res.json({ id: order.id });
});What is wrong
Idempotency keys are the standard pattern for any state-changing API that can be retried. The client generates a unique key per logical attempt; the server dedupes by it. Stripe, AWS, Razorpay all expose this and document it as required for mutating calls. Without it, every retry creates a new resource — mobile networks are noisy, double-clicks are real, the bug shows up under load.
The attack
Symptom:
# User on slow mobile network taps Place Order. 7s pass.
# Frustrated, taps again. Both requests reach the server.
# Two orders. Two fulfillment workflows. Two charges.Common cause of e-commerce support tickets.
Mesrai's review comment
mesraipilot · Bot · reviewed 1 min ago
[mesrai] [code-review] [Logic] [Idempotency] [high]
POST that creates resources without idempotency key. Network retries
and double-clicks create duplicates.
Require an Idempotency-Key header; dedupe by upsert:
const key = req.header("Idempotency-Key");
if (!key) return res.status(400).json({ error: "Idempotency-Key required" });
const order = await db.orders.upsert({
where: { idempotencyKey: key },
create: { ...data, idempotencyKey: key, userId: req.user.id },
update: {}, // existing returned as-is
});
return res.json({ id: order.id });
Frontend: generate UUID per submit attempt, persist in
sessionStorage so retries reuse it.The fix
// api/orders.ts — fixed
ordersRouter.post("/orders", requireAuth, async (req, res) => {
const key = req.header("Idempotency-Key");
if (!key) return res.status(400).json({ error: "Idempotency-Key required" });
const order = await db.orders.upsert({
where: { idempotencyKey: key },
create: {
idempotencyKey: key, userId: req.user.id,
items: req.body.items, total: req.body.total,
},
update: {},
});
return res.json({ id: order.id });
});Upsert by idempotency key. Retries return the same order. Frontend reuses the key on retry. Class closed.
Why human review missed it
Idempotency is missing on most pre-production POST endpoints because the happy path doesn't need it. The bug surfaces under retry, flakiness, double-clicks — all common in production. Mesrai catches every state-changing POST/PUT/PATCH/DELETE without an idempotency key check.
Related rules + further reading
Mesrai rule pack: logic/post-idempotency-required — flags mutating endpoints without Idempotency-Key validation.
Stripe docs: Idempotent Requests.
Same shape as cluster 3 post 27 — different surface, same principle.
Takeaway
Every mutating POST needs an idempotency key. Generated by client, deduped by server. Mesrai catches the gap.