Skip to content
WebCureby Swasti
Critical

Other users' data by changing an ID

Change /invoices/123 to /invoices/124 and see someone else's invoice.

What it is

An API checks that you're logged in, but not that the record you asked for belongs to you. Known as IDOR (insecure direct object reference), it's one of the most common holes in SaaS apps.

Why fast-built apps get it

Generated CRUD endpoints fetch by ID because that's what the prompt asked for. Ownership checks are an extra line nobody asked for.

Before and after the cure

app/api/invoices/[id]/route.tsVulnerable
export async function GET(req, { params }) {
  await requireUser(req);
  const { id } = await params;
  return Response.json(await db.invoice.findUnique({ where: { id } }));
}
app/api/invoices/[id]/route.tsCured
export async function GET(req, { params }) {
  const user = await requireUser(req);
  const { id } = await params;
  const invoice = await db.invoice.findFirst({
    where: { id, teamId: user.teamId },   // must belong to their team
  });
  if (!invoice) return new Response("Not found", { status: 404 });
  return Response.json(invoice);
}

What it can cost you

  • Customer invoices, documents or chats exposed to other customers
  • Users editing or deleting other users' records
  • A data-breach notification you have to send

Check your own app

  • Log in as user A, copy a resource URL, open it as user B
  • Look for API routes that query by id without also filtering by user or team
  • Check sequential IDs in URLs; they make guessing trivial

The cure

  • Scope every query by the current user or team, on the server
  • Use random IDs (UUIDs) instead of counters
  • Add automated tests that try cross-account access

Does your app have this one?

We'll check this and every other loophole on your app, then give you the cure.

Talk to us on WhatsApp