IDOR Vulnerability: What AI Code Gets Wrong
IDOR vulnerabilities let anyone access other users' data by changing an ID in the URL. How AI code creates them, real examples, and how to fix yours.
An IDOR vulnerability (insecure direct object reference) is what happens when your app hands back someone else’s data just because the request contained their ID. The server looks up the record, finds it, and returns it without ever asking whether the person making the request should see it. It’s the number-one item in the OWASP broken access control category, and it’s the single most common security hole in apps built with AI code generators.
The reason it shows up so often in AI-generated code is simple: the code works. A route that fetches an order by ID and returns the JSON is correct from a functionality standpoint, and neither Cursor nor Copilot will flag the missing ownership check because a missing check doesn’t produce an error. The route compiles, the tests pass, the demo looks great, and you ship a vulnerability that lets any authenticated user read every other user’s records by incrementing a number in the URL.
What is an IDOR vulnerability?
IDOR stands for insecure direct object reference. It means the application takes an identifier supplied by the user (an ID in the URL, a parameter in the request body, a filename in a query string) and uses it to look up an internal object (a database row, a file, an API resource) without verifying that the requesting user has permission to access that specific object.

The key word is “direct”, because the application exposes its internal references directly to the user and trusts the user not to change them, which is exactly where the vulnerability lives. A user who changes /api/orders/1042 to /api/orders/1043 shouldn’t get someone else’s order, but in an IDOR-vulnerable application, they do, because the server runs the same query regardless of who asked.
IDOR sits under the OWASP broken access control category, which has been the number-one web application security risk since the 2021 OWASP Top 10 and holds that position in the 2025 edition. It’s ranked first because it’s the most common and the easiest to exploit, and developers skip it more than any other category because the vulnerable code looks perfectly normal.
Why does AI-generated code create IDOR bugs?
AI code generators optimize for code that runs. When you ask Cursor, Copilot, Lovable, or Bolt to “create an API endpoint that returns an order by ID”, the model produces a route that does exactly that. It accepts the ID, queries the database, and returns the result.

What it doesn’t do, unless you specifically ask, is add a WHERE user_id = currentUser.id clause. The generated code satisfies the functional requirement (fetch an order by ID), and the authorization requirement (only let the owner see their own order) was never part of the prompt. The model has no reason to add it, and a missing authorization check produces no error and no failed test. Everything appears to work because everything does work, for the wrong people.
This pattern repeats across every AI coding tool and every framework. A vibe-coded app might have fifty endpoints that all function perfectly and all share the same flaw: they return whatever you ask for, to anyone who asks. The developer tested each endpoint by loading their own data with their own ID, confirmed it worked, and moved to the next feature. The possibility that someone might send a different ID never came up, because the AI never raised it.
If you’ve built an app with AI-generated code and haven’t specifically audited every data-fetching route for ownership validation, the odds are good that at least some of them are vulnerable to IDOR. The article on vibe coding security risks covers the broader pattern, but IDOR deserves its own deep look because it’s the most frequent and the most quietly damaging.
How does an IDOR attack actually work?
The attack itself is almost embarrassingly simple. An attacker doesn’t need special tools, a proxy, or deep technical knowledge. They open their browser’s developer tools, look at the network requests their own actions produce, spot a numeric or predictable ID in a URL or request body, and change it.

Consider what this looks like in practice: a user is logged into a SaaS app. They visit their own profile at /api/users/847. Their browser’s network tab shows a GET request to that URL returning their name, email, and billing info. They change the URL to /api/users/848 and hit enter. If the server returns another user’s profile, the app has an IDOR vulnerability.
The same logic applies to POST, PUT, and DELETE endpoints. An attacker who can change an order ID in a cancellation request can cancel other users’ orders. One who can modify a document ID in an update request can overwrite other users’ files. The impact scales with the sensitivity of the data and the scope of the available actions.
IDOR attacks fall into two categories. Horizontal privilege escalation means accessing another user’s data at the same permission level (user A reads user B’s orders). Vertical privilege escalation means accessing data or actions reserved for a higher role (a regular user accessing admin-only records by changing an ID). Both happen through the same mechanism: the server trusts the supplied identifier without checking who’s asking.
What makes IDOR particularly dangerous is that it leaves almost no trace. There’s no brute-force login attempt, no SQL injection payload, no unusual traffic pattern. The requests look identical to normal usage. The only difference is the ID value, and most logging configurations don’t flag a valid-looking request for a different user’s resource.
What are the most common IDOR patterns?
IDOR shows up in several distinct patterns, and recognizing them is the first step to fixing them. Each pattern involves the same core mistake (trusting user-supplied identifiers) applied in a different context.

The most common is the URL parameter pattern. This is GET /api/orders/:id where the route handler takes the ID from the URL and queries the database with it directly. The fix is adding the authenticated user’s ID to the query so it only returns records that belong to them.
The request body pattern appears in POST and PUT endpoints. A form submission includes a user_id field, and the server uses that value to associate the data. An attacker changes the field to another user’s ID and the server obediently processes it. The fix is ignoring any user ID in the request body and always pulling the user’s identity from the authenticated session.
The file reference pattern uses filenames or file paths as direct references. An endpoint like GET /api/documents/invoice-2024-1042.pdf serves any file if the requester knows or guesses the naming convention. Sequential filenames, date-based names, or predictable patterns make guessing trivial. The fix is either mapping files to indirect references (a random token per file) or adding an ownership lookup before serving.
The GraphQL variant deserves specific attention because GraphQL APIs are common in AI-generated apps and their resolver structure makes IDOR particularly easy to overlook. A resolver that accepts an id argument and fetches the object without checking permissions is the GraphQL equivalent of an unprotected REST endpoint, but it’s buried in a schema file rather than a route handler, which makes it easier to miss during review.
The API key pattern is subtler. Some apps use API keys that encode the user’s identity or permissions, and the key itself becomes the direct object reference. If the key format is predictable or if the server only checks that a valid key was sent (without matching it to the requested resource), the same IDOR logic applies.
How do you prevent IDOR in your code?
The prevention principle is straightforward: every route that accesses user-specific data must verify that the authenticated user owns the requested resource. The query itself, not a middleware filter on top of it, should enforce the constraint. If the ownership check lives only in a middleware that someone forgets to apply to a new route, the vulnerability reappears.

In a Next.js App Router API route, a vulnerable handler looks like this: it reads the id from the request params and queries SELECT * FROM orders WHERE id = $1. The secure version adds the authenticated user’s ID to the query: SELECT * FROM orders WHERE id = $1 AND user_id = $2. If the order doesn’t belong to the requesting user, the query returns nothing, and the server responds with a 404.
In Express with a SQL database, the same principle applies. The route handler pulls the user from req.session.userId (never from the request body or a client-supplied header) and includes it in the query’s WHERE clause, using parameterized queries to avoid SQL injection at the same time. A middleware can enforce that req.session.userId exists (authentication), but the ownership check belongs in the query itself (authorization).
In Django, the ORM makes this clean. Instead of Order.objects.get(id=order_id), you write Order.objects.get(id=order_id, user=request.user). If the object doesn’t exist or doesn’t belong to the requesting user, Django raises a DoesNotExist exception and the view returns a 404. The pattern is the same in every framework: scope every data query to the authenticated user.
For apps that use Supabase, row-level security (RLS) policies handle this at the database layer. An RLS policy on the orders table that enforces user_id = auth.uid() means the database itself refuses to return rows that don’t belong to the requesting user, regardless of what the application code does. This is particularly useful for securing a Next.js + Supabase app because it catches IDOR even in routes where the developer forgot an ownership check in the application code.
A few additional practices reduce your exposure further. Use UUIDs instead of sequential integers for public-facing identifiers, because they’re much harder to guess, but remember that UUIDs don’t replace authorization checks. An attacker who obtains a UUID through a shared link, a browser’s address bar, or a log file can still exploit an IDOR if the server doesn’t verify ownership. Centralize your authorization logic in a service layer or middleware that every data-access route must use, so a new endpoint can’t accidentally skip the check by being wired differently.
How do you test your app for IDOR?
Testing for IDOR is the part most teams skip, partly because the vulnerability is invisible from the perspective of normal user testing (you log in, load your data, everything works) and partly because most testing frameworks aren’t designed to simulate cross-user access.

Manual testing is the most reliable method for catching IDOR. Create two test accounts in your app with different data. Log into account A, perform every action the app supports, and record every request that includes an identifier (user ID, order ID, document ID, any parameter that references a specific resource). Then, still authenticated as account A, replay each request but swap the identifiers with values from account B. If any response returns account B’s data, that endpoint is vulnerable.
Automated tools can detect some IDOR patterns. Scanners look for sequential IDs in responses, missing authorization headers on data endpoints, and predictable object reference formats. They’re good at finding the obvious cases (sequential numeric IDs in URLs without auth) but can’t fully test business logic, so they work best as a first pass before manual testing.
A practical approach for apps built with AI code generators is to audit every route file in your codebase and look for any database query or data fetch that doesn’t include the authenticated user’s ID as a filter. In a Next.js or Express app, search for patterns like findOne({ id }) or WHERE id = ? without an AND user_id = ? clause. This textual search catches the majority of IDOR vulnerabilities because they all share the same structural pattern: a lookup by the supplied ID without an ownership constraint.
Running a security scan on your site before launch catches the surface-level indicators (exposed endpoints, missing authentication, predictable references) and gives you a starting point. Each finding comes with a plain-English explanation and a copy-paste fix prompt you can hand to your AI coding tool, so the same workflow that created the vulnerability can fix it.
What happens when IDOR goes undetected?
The real-world consequences of IDOR vulnerabilities range from embarrassing to catastrophic, depending on what data the app exposes and how many users it has.

Data exposure is the most common outcome. An attacker iterates through a range of IDs and downloads every user’s profile, order history, medical records, financial documents, or whatever the application stores. Because the requests look normal to the server, the exfiltration can happen over hours or days without triggering any rate-limit or anomaly detection. By the time anyone notices, the data is gone.
Data modification is the next level. IDOR vulnerabilities in write endpoints let an attacker update other users’ profiles, change their email addresses to take over accounts, modify pricing, cancel subscriptions, or delete records. An attacker who can change the email address on another user’s account through an IDOR in the profile update endpoint effectively owns that account from that point forward.
The business impact extends beyond the data itself. A public IDOR disclosure, which is increasingly common as security researchers report through bug bounty programs and responsible disclosure platforms, damages user trust in a way that’s hard to recover from. Users expect that their data is only visible to them, and discovering that anyone could have read their records by changing a number in a URL is a violation of that basic expectation.
For apps subject to GDPR, CCPA, or similar regulations, an IDOR vulnerability that exposes personal data is a reportable breach. The CCPA compliance checklist and the cookie consent requirements cover the privacy side, but access control is the technical foundation that makes those guarantees enforceable. Without it, every privacy promise on your site is hollow.
IDOR prevention checklist before launch
Before you ship or update an app, walk through this checklist once. It takes less time than debugging a breach.

Every route that returns or modifies user-specific data should include the authenticated user’s ID in the database query, not as a separate check on top of the result but as part of the query’s WHERE clause. Search your route handlers for any query that takes an ID from the request without also filtering by the session user.
Every identifier exposed in URLs, response bodies, or client-side code should be a UUID or another non-sequential format. Sequential integers make enumeration trivial, and switching to UUIDs reduces the attack surface even if it doesn’t replace the need for authorization checks.
Every file-serving endpoint should validate that the requesting user owns the file before returning it, and filenames should not follow a predictable pattern that allows guessing.
GraphQL resolvers should check permissions at the resolver level, not only at the query level, because a nested resolver that fetches related objects can bypass a top-level permission check if it doesn’t enforce its own ownership constraint.
No route should trust a user ID or role sent in the request body or a custom header. The user’s identity comes from the server-side session or the verified JWT, and any client-supplied identity field is a vector for IDOR.
Run a website security checklist and a security scan against your live deployment. The scan catches exposed endpoints and missing authorization that a code review might miss, and the security scan gives you a fix prompt for each finding that you can paste directly into Cursor or your AI tool to generate the patch.
An IDOR (insecure direct object reference) vulnerability happens when an application uses a user-supplied identifier, like an ID in the URL or request body, to fetch data without checking whether the logged-in user actually owns that data. An attacker changes the ID to someone else's and gets their records back, because the server never verified ownership.
AI code generators produce routes that work, and working means the data loads when you pass an ID. They rarely add ownership checks unless you explicitly ask, because a missing authorization check doesn't throw an error. The route runs, the tests pass, and the vulnerability ships with it.
IDOR is a specific type of broken access control. Broken access control is the broad OWASP category covering any failure to restrict what authenticated users can do. IDOR is the most common form: using a direct object reference (a database ID, a filename, an API parameter) without verifying the requester has permission to access that specific object.
Add an ownership check to every route that returns user-specific data. The query itself should filter by both the requested ID and the authenticated user's ID, so a mismatched request returns nothing instead of someone else's data. Use middleware to centralize this check, and avoid relying on unguessable IDs alone, because UUIDs don't replace authorization.
Automated scanners can detect some IDOR patterns, especially sequential ID exposure and missing authorization headers, but they can't fully test business logic. A scanner like Amabrik's security scan catches the surface-level indicators (exposed endpoints, missing auth, predictable object references) and gives you a copy-paste fix prompt for each finding, which covers the majority of real-world IDOR cases in production apps.
No. UUIDs make object references harder to guess, but they don't replace authorization. If an attacker obtains a UUID through a leaked URL, a shared link, browser history, or a log file, they can still access the resource if the server doesn't check ownership. UUIDs are a useful layer, not a fix on their own.


