VibeSecurity

Security check

Broken access control and IDOR: making sure users only see their own data

Access control bugs happen when a route trusts an identifier from the request and never checks that the caller may use it. They are among the most common serious issues in quickly built apps.

By the VibeSecurity team1 min read

What the bug looks like

A logged-in user requests /api/orders/1041 and gets an order. They change the number to 1042 and get someone else's order, because the route only checks that a session exists.

Fix it with an ownership filter

Route handler
const order = await db.order.findFirst({
  where: { id: params.id, userId: session.user.id },
});
if (!order) return new Response('Not found', { status: 404 });

Test it with two accounts

  • Create two test users on your own app and place data under each.
  • Log in as user A, copy a request that reads or changes user A's record, and replay it with user B's session.
  • The response must be 403 or 404. Repeat for read, update and delete on every resource type.
  • Do the same for admin routes using a normal user.

Common blind spots

  • List endpoints that return all rows instead of the caller's rows.
  • Nested resources where the parent is checked but the child id is not.
  • Functions that use a service-level database key and skip row-level rules.
  • Buttons hidden in the UI while the underlying route stays open.

Frequently asked questions

Do random UUIDs prevent IDOR?

They make guessing harder but do not enforce access. If a UUID leaks, an unchecked route still serves the data.

Should I return 403 or 404?

Returning 404 for records the caller may not see avoids confirming that the record exists.

Sources

  1. 1.OWASP Insecure Direct Object Reference Prevention Cheat Sheet
  2. 2.OWASP Top 10: Broken Access Control