IDOR Vulnerability on REST Endpoint
A REST endpoint returns resources by id without verifying the caller owns that resource. Any authenticated user can access any other user's data by changing the id in the URL.
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