[{"data":1,"prerenderedAt":352},["ShallowReactive",2],{"blog-vulnerabilities/idor":3},{"id":4,"title":5,"body":6,"category":328,"date":329,"dateModified":329,"description":330,"draft":331,"extension":332,"faq":333,"featured":331,"headerVariant":337,"image":338,"keywords":338,"meta":339,"navigation":340,"ogDescription":341,"ogTitle":338,"path":342,"readTime":343,"schemaOrg":344,"schemaType":345,"seo":346,"sitemap":347,"stem":348,"tags":349,"twitterCard":350,"__hash__":351},"blog/blog/vulnerabilities/idor.md","IDOR Explained: Insecure Direct Object Reference",{"type":7,"value":8,"toc":309},"minimark",[9,16,21,24,27,42,46,49,58,61,65,70,79,83,92,96,105,109,169,179,183,187,196,200,209,213,216,225,229,232,241,250,278,297],[10,11,12],"tldr",{},[13,14,15],"p",{},"IDOR happens when your app uses predictable IDs (like /user/123) without checking if the logged-in user should access that resource. Attackers simply change the ID to access other users' data. The fix: always verify the requesting user owns or has permission to access the requested resource before returning it.",[17,18,20],"h2",{"id":19},"what-is-idor","What Is IDOR?",[13,22,23],{},"IDOR (Insecure Direct Object Reference) is one of the most common vulnerabilities in web applications. It occurs when an application exposes a reference to an internal object (like a database record) and doesn't verify whether the user should have access to that object.",[13,25,26],{},"Here's a simple example:",[28,29,31],"code-block",{"label":30},"IDOR vulnerable endpoint",[32,33,38],"pre",{"className":34,"code":36,"language":37},[35],"language-text","// URL: /api/invoices/1001\n// User A is logged in and can see their invoice\n\n// User A changes the URL to: /api/invoices/1002\n// Now they can see User B's invoice!\n\n// The server code:\napp.get('/api/invoices/:id', async (req, res) => {\n  const invoice = await db.invoice.findUnique({\n    where: { id: parseInt(req.params.id) }\n  });\n  res.json(invoice); // No check if user owns this invoice!\n});\n","text",[39,40,36],"code",{"__ignoreMap":41},"",[17,43,45],{"id":44},"why-idor-is-so-common-in-vibe-coded-apps","Why IDOR Is So Common in Vibe-Coded Apps",[13,47,48],{},"AI code generators create functional CRUD operations but often skip authorization checks. When you ask for \"an API to fetch invoices,\" you get:",[28,50,52],{"label":51},"What AI generates (vulnerable)",[32,53,56],{"className":54,"code":55,"language":37},[35],"// AI-generated code - works but insecure\napp.get('/api/invoices/:id', async (req, res) => {\n  const invoice = await prisma.invoice.findUnique({\n    where: { id: req.params.id }\n  });\n  if (!invoice) return res.status(404).json({ error: 'Not found' });\n  res.json(invoice);\n});\n\napp.delete('/api/invoices/:id', async (req, res) => {\n  await prisma.invoice.delete({\n    where: { id: req.params.id }\n  });\n  res.json({ success: true });\n});\n",[39,57,55],{"__ignoreMap":41},[13,59,60],{},"The code works perfectly for its intended use case. The problem is it works for attackers too.",[17,62,64],{"id":63},"common-idor-patterns","Common IDOR Patterns",[66,67,69],"h3",{"id":68},"_1-sequential-ids-in-urls","1. Sequential IDs in URLs",[28,71,73],{"label":72},"Easy to enumerate",[32,74,77],{"className":75,"code":76,"language":37},[35],"/api/orders/1001  → /api/orders/1002 → /api/orders/1003\n/users/profile/42 → /users/profile/43 → /users/profile/44\n/documents/download/1 → /documents/download/2\n",[39,78,76],{"__ignoreMap":41},[66,80,82],{"id":81},"_2-hidden-form-fields","2. Hidden Form Fields",[28,84,86],{"label":85},"User ID in form data",[32,87,90],{"className":88,"code":89,"language":37},[35],"\u003Cform action=\"/api/update-profile\" method=\"POST\">\n  \u003Cinput type=\"hidden\" name=\"userId\" value=\"123\" />\n  \u003C!-- Attacker changes this to another user's ID -->\n\u003C/form>\n",[39,91,89],{"__ignoreMap":41},[66,93,95],{"id":94},"_3-api-parameters","3. API Parameters",[28,97,99],{"label":98},"Modifiable request body",[32,100,103],{"className":101,"code":102,"language":37},[35],"// Original request from app:\nPOST /api/transfer\n{ \"fromAccount\": \"user123\", \"toAccount\": \"shop\", \"amount\": 50 }\n\n// Attacker modifies:\nPOST /api/transfer\n{ \"fromAccount\": \"otherUser456\", \"toAccount\": \"attacker\", \"amount\": 5000 }\n",[39,104,102],{"__ignoreMap":41},[17,106,108],{"id":107},"real-consequences-of-idor","Real Consequences of IDOR",[110,111,112,125],"table",{},[113,114,115],"thead",{},[116,117,118,122],"tr",{},[119,120,121],"th",{},"Endpoint Type",[119,123,124],{},"IDOR Consequence",[126,127,128,137,145,153,161],"tbody",{},[116,129,130,134],{},[131,132,133],"td",{},"User profiles",[131,135,136],{},"View private information, emails, addresses",[116,138,139,142],{},[131,140,141],{},"Financial records",[131,143,144],{},"Access invoices, payment history, account balances",[116,146,147,150],{},[131,148,149],{},"Documents/files",[131,151,152],{},"Download confidential documents",[116,154,155,158],{},[131,156,157],{},"Delete operations",[131,159,160],{},"Delete other users' data",[116,162,163,166],{},[131,164,165],{},"Admin functions",[131,167,168],{},"Access admin panels, modify settings",[170,171,172],"danger-box",{},[13,173,174,178],{},[175,176,177],"strong",{},"GDPR impact:"," IDOR that exposes personal data can result in data breach notification requirements and significant fines under privacy regulations.",[17,180,182],{"id":181},"how-to-fix-idor","How to Fix IDOR",[66,184,186],{"id":185},"_1-always-verify-ownership","1. Always Verify Ownership",[28,188,190],{"label":189},"Proper authorization check",[32,191,194],{"className":192,"code":193,"language":37},[35],"// SECURE: Check user owns the resource\napp.get('/api/invoices/:id', authenticate, async (req, res) => {\n  const invoice = await prisma.invoice.findUnique({\n    where: {\n      id: req.params.id,\n      userId: req.user.id  // Only return if user owns it\n    }\n  });\n\n  if (!invoice) {\n    return res.status(404).json({ error: 'Not found' });\n  }\n\n  res.json(invoice);\n});\n",[39,195,193],{"__ignoreMap":41},[66,197,199],{"id":198},"_2-use-session-data-for-user-identity","2. Use Session Data for User Identity",[28,201,203],{"label":202},"Never trust client-provided user IDs",[32,204,207],{"className":205,"code":206,"language":37},[35],"// WRONG: User ID from request body\napp.post('/api/update-profile', async (req, res) => {\n  await updateUser(req.body.userId, req.body.data); // Vulnerable!\n});\n\n// CORRECT: User ID from authenticated session\napp.post('/api/update-profile', authenticate, async (req, res) => {\n  await updateUser(req.user.id, req.body.data); // Secure!\n});\n",[39,208,206],{"__ignoreMap":41},[66,210,212],{"id":211},"_3-use-indirect-references","3. Use Indirect References",[13,214,215],{},"Map public-facing IDs to user-specific references:",[28,217,219],{"label":218},"Indirect reference mapping",[32,220,223],{"className":221,"code":222,"language":37},[35],"// Instead of /api/documents/42 (database ID)\n// Use /api/documents/doc_a3f7 (user-specific reference)\n\n// User A's documents: doc_a1, doc_a2, doc_a3\n// User B's documents: doc_b1, doc_b2, doc_b3\n// These map to different database IDs for each user\n\napp.get('/api/documents/:ref', authenticate, async (req, res) => {\n  const doc = await getDocumentByUserRef(req.user.id, req.params.ref);\n  if (!doc) return res.status(404).json({ error: 'Not found' });\n  res.json(doc);\n});\n",[39,224,222],{"__ignoreMap":41},[66,226,228],{"id":227},"_4-implement-row-level-security","4. Implement Row-Level Security",[13,230,231],{},"For Supabase or PostgreSQL, use RLS to enforce access at the database level:",[28,233,235],{"label":234},"Supabase Row Level Security",[32,236,239],{"className":237,"code":238,"language":37},[35],"-- Users can only see their own invoices\nCREATE POLICY \"Users see own invoices\" ON invoices\n  FOR SELECT USING (auth.uid() = user_id);\n\n-- Users can only update their own invoices\nCREATE POLICY \"Users update own invoices\" ON invoices\n  FOR UPDATE USING (auth.uid() = user_id);\n",[39,240,238],{"__ignoreMap":41},[242,243,244],"success-box",{},[13,245,246,249],{},[175,247,248],{},"Defense in depth:"," Even with RLS, implement authorization checks in your application code. Multiple layers of protection ensure security even if one layer fails.",[251,252,253,260,266,272],"faq-section",{},[254,255,257],"faq-item",{"question":256},"What is IDOR?",[13,258,259],{},"IDOR (Insecure Direct Object Reference) is when an application exposes internal object identifiers (like database IDs) without proper authorization checks. Attackers can access other users' data by simply changing ID values in URLs or API requests.",[254,261,263],{"question":262},"Does using UUIDs prevent IDOR?",[13,264,265],{},"UUIDs make IDOR harder to exploit because they're not sequential, but they don't prevent it. Attackers might find UUIDs through other parts of your application. Always implement proper authorization checks regardless of ID format.",[254,267,269],{"question":268},"How common is IDOR in AI-generated code?",[13,270,271],{},"IDOR is extremely common in AI-generated code. AI tools often generate functional CRUD endpoints without proper authorization checks. The code works correctly for the happy path but doesn't verify users can only access their own data.",[254,273,275],{"question":274},"How do I test for IDOR?",[13,276,277],{},"Create two test accounts. Log in as user A, access a resource, then change the ID to a resource belonging to user B. If you can access user B's data, you have an IDOR vulnerability. Test all endpoints that use IDs.",[279,280,281,287,292],"related-articles",{},[282,283],"related-card",{"description":284,"href":285,"title":286},"Parent vulnerability category","/blog/vulnerabilities/broken-access-control","Broken Access Control",[282,288],{"description":289,"href":290,"title":291},"Database-level protection","/blog/how-to/setup-supabase-rls","Set Up Supabase RLS",[282,293],{"description":294,"href":295,"title":296},"Comprehensive API security","/blog/checklists/api-security-checklist","API Security Checklist",[298,299,302,306],"cta-box",{"href":300,"label":301},"/","Start Free Scan",[17,303,305],{"id":304},"find-idor-vulnerabilities","Find IDOR Vulnerabilities",[13,307,308],{},"Our scanner tests your API endpoints for authorization issues.",{"title":41,"searchDepth":310,"depth":310,"links":311},2,[312,313,314,320,321,327],{"id":19,"depth":310,"text":20},{"id":44,"depth":310,"text":45},{"id":63,"depth":310,"text":64,"children":315},[316,318,319],{"id":68,"depth":317,"text":69},3,{"id":81,"depth":317,"text":82},{"id":94,"depth":317,"text":95},{"id":107,"depth":310,"text":108},{"id":181,"depth":310,"text":182,"children":322},[323,324,325,326],{"id":185,"depth":317,"text":186},{"id":198,"depth":317,"text":199},{"id":211,"depth":317,"text":212},{"id":227,"depth":317,"text":228},{"id":304,"depth":310,"text":305},"vulnerabilities","2026-01-15","IDOR lets attackers access other users' data by changing IDs in URLs or requests. Learn how this common vulnerability works and how to protect your vibe-coded app.",false,"md",[334,335,336],{"question":256,"answer":259},{"question":262,"answer":265},{"question":268,"answer":271},"red",null,{},true,"Learn how IDOR attacks let hackers access other users' data and how to prevent them.","/blog/vulnerabilities/idor","8 min read","[object Object]","TechArticle",{"title":5,"description":330},{"loc":342},"blog/vulnerabilities/idor",[],"summary_large_image","tYOzRVzqGFBVbaONtsSLadVuo0NVhHsYd9VbOgPLui4",1775843926875]