BOLA simulator
Pick a logged-in user and a target order ID. The same request goes to two endpoint implementations: a naive GET /orders/:id and a hardened one. The hardened endpoint scopes the lookup by owner; the naive one trusts the URL. Try Alice reading Bob's order.
Naive endpoint
GET /orders/:id → WHERE id = ?{
"id": "ord_1001",
"ownerId": "u_alice",
"total": 84.5,
"shippingAddress": "123 Maple St, Austin TX",
"paymentLast4": "4242",
"notes": "Leave at door."
}Hardened endpoint
GET /orders/:id → WHERE id = ? AND owner_id = ?{
"id": "ord_1001",
"ownerId": "u_alice",
"total": 84.5,
"shippingAddress": "123 Maple St, Austin TX",
"paymentLast4": "4242",
"notes": "Leave at door."
}What just happened
The naive endpoint authenticates the request, then does SELECT * FROM orders WHERE id = ? — that's BOLA01. The hardened endpoint adds AND owner_id = ? in the same query (not a separate post-fetch check), and returns 404 — not 403 — for cross-owner access so existence isn't leaked (BOLA02).
Try every combination of (user, order) and watch the table fill in. The naive column shows other people's shipping addresses, payment last-4, and notes. The hardened column shows the same 404 it returns for an order that doesn't exist at all.