IDOR Vulnerability on REST Endpoint
A REST endpoint returns resources by id without checking the caller owns them, so any signed-in user can read another user's data by changing the id.
Typical error
Resource accessible via id change without ownership check
What this is
Insecure Direct Object Reference (IDOR) is the "change a number in the URL, see someone else's stuff" bug. A typical AI-generated endpoint:
// GET /api/invoices/[id]
export async function GET(
_req: Request,
{ params }: { params: Promise<{ id: string }> }
) {
const { id } = await params
const invoice = await db.invoice.findUnique({ where: { id } })
return Response.json(invoice)
}Any logged-in user can fetch /api/invoices/123, then /api/invoices/124, etc., and see every invoice in the system.
Why AI tools ship this
Scaffold tools generate CRUD endpoints that match the database schema. They authenticate the request (is the user logged in?) but skip authorization (does this user own this specific row?).
How to detect
Audit every dynamic API route. For each, answer:
- Does the resource belong to a user?
- Does the handler verify
req.user.idmatches the owner? - If ownership is enforced only in the database, is RLS actually enabled?
How to fix
Three options, from weakest to strongest:
-
App-level ownership check
const invoice = await db.invoice.findUnique({ where: { id } }) if (!invoice || invoice.userId !== session.userId) { return new Response('Not found', { status: 404 }) } -
Scoped query (preferred)
const invoice = await db.invoice.findFirst({ where: { id, userId: session.userId }, }) -
Database-level policy (most robust)
Enable row level security so the database itself filters on
auth.uid() = owner_id. No app-level bug can bypass it.
Always return 404 on ownership mismatch, not 403. A 403 reveals that the resource exists.
Related
- Glossary: IDOR
- Glossary: auth bypass
- Glossary: row level security