High CWE-639 OWASP A01:2021

IDOR in Vibe Coded Apps

How to find and fix the vulnerability that lets users access each other's data

Quick Answer

IDOR is when users can access other users' data by changing IDs in URLs or requests. Change /api/orders/123 to /api/orders/456 and see someone else's order. Part of #1 on OWASP Top 10. AI tools check auth but skip authorization.

#1
OWASP Ranking
CWE-639
94%
Apps affected (OWASP)
High
Severity

Source: OWASP Top 10 (2021)

What is IDOR?

IDOR (Insecure Direct Object Reference) is when an application exposes internal references like database IDs and fails to verify that the requesting user has permission to access that specific resource.

Think of it like an apartment building where every door has a number but no locks. You live in apartment 203, but nothing stops you from opening door 456 and walking into someone else's home. The building knows who you are (authentication), but it does not check if you should access that specific apartment (authorization).

According to OWASP Top 10 (2021), Broken Access Control (which includes IDOR) ranks #1 in web application security risks. OWASP found that 94% of applications tested had some form of broken access control. IDOR vulnerabilities are frequently reported on HackerOne, often earning significant bounties due to their high impact. For vibe coders, IDOR is especially dangerous because AI tools typically add authentication but not per-resource authorization.

How do AI tools create IDOR vulnerabilities?

AI tools create IDOR vulnerabilities by implementing authentication (checking if someone is logged in) but skipping authorization (checking if they should access this specific resource).

Common AI-generated vulnerable patterns

When you ask AI tools for CRUD endpoints, they often generate code like this:

// AI-generated endpoint - has auth but no authorization
export async function GET(
  request: Request,
  { params }: { params: { id: string } }
) {
  const session = await getServerSession(authOptions)

  if (!session) {
    return Response.json({ error: 'Unauthorized' }, { status: 401 })
  }

  // VULNERABLE: Returns ANY order, not just user's orders
  const order = await db.order.findUnique({
    where: { id: params.id }
  })

  return Response.json(order)
}

// User A calls: GET /api/orders/order-123 ✓ Gets their order
// User A calls: GET /api/orders/order-456 ✓ Gets User B's order!

This code checks authentication (user is logged in) but not authorization (user owns this order). Any authenticated user can access any order.

Why this happens: When vibe coding and you ask for "an API to get order details," AI understands you need auth and adds it. But authorization requires understanding your data model, specifically which orders belong to which users. AI does not automatically infer this relationship from a simple prompt.

All major AI coding tools (Cursor, Claude Code, Bolt, v0, Windsurf, and GitHub Copilot) exhibit this pattern. The fix requires explicitly checking resource ownership in every endpoint.

What could happen if I have IDOR?

IDOR can result in complete data breach, privacy violations, data manipulation, and financial fraud.

  • Mass data theft: Attackers enumerate IDs (123, 124, 125...) to download all user data, orders, messages, or documents
  • Privacy violations: Access personal information, medical records, financial data, private messages of other users
  • Data manipulation: Modify other users' profiles, change their settings, update their records
  • Financial fraud: View other users' payment info, modify transactions, access invoices with sensitive data
  • Account takeover: Access password reset tokens, session data, or account recovery information for other users

How do I detect IDOR?

Detect IDOR by reviewing every endpoint that takes an ID parameter and checking if it verifies the user owns or has permission to access that resource.

Red flags to look for
// Pattern 1: Direct database lookup with user-provided ID
const order = await db.order.findUnique({
  where: { id: params.id }  // No check that user owns this order
})

// Pattern 2: Auth check but no ownership check
if (!session) return unauthorized()
const document = await getDocument(params.documentId)  // Any user's document

// Pattern 3: Filtering by ID but not by user
const invoice = invoices.find(i => i.id === params.invoiceId)  // Not filtered by userId

// Testing checklist:
// 1. Find all routes with :id, [id], {id} parameters
// 2. For each, check: does it verify user owns/can access this ID?
// 3. Test: can User A access User B's resources by changing the ID?

// Manual test:
// 1. Login as User A, create an order, note the ID
// 2. Login as User B
// 3. Try to access User A's order ID - if it works, you have IDOR

Scan your endpoints automatically

Scan your code free

How do I fix IDOR?

Fix IDOR by adding authorization checks that verify the authenticated user owns or has permission to access the requested resource. Always filter queries by user ID or check ownership before returning data.

AI Fix Prompt

Copy this prompt into Cursor, Claude Code, or Bolt to automatically fix IDOR in your codebase:

Copy-paste this prompt
Fix all IDOR (Insecure Direct Object Reference) vulnerabilities in my codebase. ## What to look for Search for API routes that: 1. Take an ID parameter (params.id, req.params.id, [id], etc.) 2. Fetch a resource using that ID 3. Do NOT verify the current user owns or can access that resource Common vulnerable patterns: ```javascript // VULNERABLE: No ownership check const order = await db.order.findUnique({ where: { id: params.id } }) // VULNERABLE: Auth exists but no authorization if (!session) return unauthorized() const document = await db.document.findUnique({ where: { id } }) ``` ## How to fix Add ownership/permission checks to every resource lookup: ### Option 1: Filter by user ID in query (preferred) ```typescript // Before (vulnerable) const order = await db.order.findUnique({ where: { id: params.id } }) // After (secure) - only returns if user owns it const order = await db.order.findUnique({ where: { id: params.id, userId: session.user.id // Add ownership filter } }) if (!order) { return Response.json({ error: 'Not found' }, { status: 404 }) } ``` ### Option 2: Explicit ownership check ```typescript // Fetch then verify const order = await db.order.findUnique({ where: { id: params.id } }) if (!order) { return Response.json({ error: 'Not found' }, { status: 404 }) } // Check ownership if (order.userId !== session.user.id) { return Response.json({ error: 'Forbidden' }, { status: 403 }) } ``` ### Option 3: For shared resources, check permissions ```typescript // Document shared with multiple users const document = await db.document.findUnique({ where: { id: params.id }, include: { shares: true } }) const canAccess = document.ownerId === session.user.id || document.shares.some(s => s.userId === session.user.id) if (!canAccess) { return Response.json({ error: 'Forbidden' }, { status: 403 }) } ``` ## Resource types to check - User profiles (/api/users/[id]) - Orders (/api/orders/[id]) - Documents (/api/documents/[id]) - Messages (/api/messages/[id]) - Comments (/api/comments/[id]) - Settings (/api/settings/[id]) - Any resource with user-provided ID ## For Supabase with RLS If using Supabase, also verify RLS policies: ```sql -- Ensure RLS is enabled ALTER TABLE orders ENABLE ROW LEVEL SECURITY; -- Policy: users can only see their own orders CREATE POLICY "Users can view own orders" ON orders FOR SELECT USING (user_id = auth.uid()); ``` ## After fixing 1. Test with two accounts - User A should NOT access User B's resources 2. Return 404 (not 403) to avoid revealing that a resource exists 3. List all endpoints you modified with before/after snippets Please proceed systematically through my codebase.

Manual Fix

The fix is always the same: verify the user owns or has permission to access the resource before returning or modifying it.

VULNERABLE
// Has authentication, but no authorization
export async function GET(
  request: Request,
  { params }: { params: { id: string } }
) {
  const session = await getServerSession(authOptions)
  if (!session) {
    return Response.json({ error: 'Unauthorized' }, { status: 401 })
  }

  // IDOR: Returns ANY order, not just user's
  const order = await db.order.findUnique({
    where: { id: params.id }
  })

  return Response.json(order)
}

// Any logged-in user can view any order
SECURE
// With authorization check
export async function GET(
  request: Request,
  { params }: { params: { id: string } }
) {
  const session = await getServerSession(authOptions)
  if (!session) {
    return Response.json({ error: 'Unauthorized' }, { status: 401 })
  }

  // SECURE: Only returns if user owns this order
  const order = await db.order.findUnique({
    where: {
      id: params.id,
      userId: session.user.id  // Ownership filter
    }
  })

  if (!order) {
    // Return 404, not 403, to not reveal existence
    return Response.json({ error: 'Not found' }, { status: 404 })
  }

  return Response.json(order)
}

What changed: The query now includes userId: session.user.id which means it only returns an order if both the ID matches AND the user owns it. We return 404 instead of 403 to avoid revealing that the resource exists to unauthorized users.

Frequently asked questions

What does IDOR stand for?

IDOR stands for Insecure Direct Object Reference. It occurs when an application exposes internal object references (like database IDs) and fails to verify that the user has permission to access that specific object. The "insecure" part is the missing authorization check.

Is IDOR the same as missing authentication?

No. Missing authentication means anyone can access the endpoint. IDOR means authenticated users can access resources belonging to other users. You can have proper authentication but still have IDOR. The user is logged in, but they can change /api/orders/123 to /api/orders/456 and see someone else's order.

Why is IDOR ranked #1 on OWASP Top 10?

Broken Access Control (which includes IDOR) is ranked #1 because it is extremely common and has severe impact. Over 94% of applications tested by OWASP had some form of broken access control. IDOR is particularly prevalent in AI-generated code because AI tools focus on functionality, not authorization.

Can UUIDs prevent IDOR?

UUIDs make IDOR harder to exploit through guessing, but they do not fix the vulnerability. An attacker who obtains a UUID through other means (logs, emails, browser history) can still exploit IDOR. The only real fix is proper authorization checks that verify the user owns or has permission to access the resource.

How do I test for IDOR vulnerabilities?

Create two test accounts. Log in as User A, note the IDs in URLs and API responses. Log in as User B, try accessing User A's resources by using their IDs. If User B can see User A's data, you have IDOR. Tools like Burp Suite can automate this by automatically swapping IDs in requests.

Related content

Scan your code for IDOR vulnerabilities

Check your API endpoints for authorization issues and other common security vulnerabilities.

Try Vibeship Scanner