← marwandiallo.comlabs

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 = ?
HTTP 200
{
  "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 = ?
HTTP 200
{
  "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.