Security Education

8 min

What an access control audit actually checks for

What an access control audit actually checks for

What broken access control actually means, what IDOR looks like in a vibe-coded app, and what an access control review checks for. In plain language, no technical background required.

What broken access control actually means, what IDOR looks like in a vibe-coded app, and what an access control review checks for. In plain language, no technical background required.

Broken access control has been the number one vulnerability on the OWASP Top 10 since 2021. Not second, not trending upward from somewhere lower, consistently first. OWASP’s own testing data found that 100% of applications tested contained some form of access control weakness, and a recent penetration testing analysis found that broken access control accounted for 32% of all high severity findings across assessed applications, a 40% increase from prior years.

That said, “access control” is one of those terms that gets used fairly loosely and means something fairly specific when it actually comes up in a security context. Most founders I work with have a general sense that it has something to do with who can access what, but don’t have a clear picture of what an access control review actually looks at in practice, which makes it hard to know whether their app has issues or not.


What access control actually means

Access control is the set of mechanisms that determine which users can do what inside your application. Not just whether someone is logged in, but whether the specific action they’re trying to perform is something they’re actually supposed to be allowed to do.

There are two distinct types worth understanding separately, because they represent different failure modes and show up in different ways during an audit.

Horizontal access control is about users at the same privilege level being kept within their own data. User A should only be able to see and interact with User A’s records. User B’s data should be completely inaccessible to User A, even though both are regular authenticated users of the same application. When this breaks, it’s typically called an IDOR (Insecure Direct Object Reference), and it’s one of the most commonly exploitable issues I come across in AI-built apps.

Vertical access control is about users being restricted to the privilege level they’ve been assigned. A regular user shouldn’t be able to access admin functions. A free tier user shouldn’t be able to access paid features. When this breaks, it’s called privilege escalation, and Apiiro’s analysis of AI-generated code in Fortune 50 enterprises found privilege escalation paths were 322% more common in AI-generated code compared to human-written equivalents. That number is worth sitting with.


What IDOR actually looks like in a vibe coded app

IDOR is probably the most consistently present access control finding across the apps I audit, and it’s worth explaining concretely because it’s genuinely simple to exploit once you understand the pattern.

The scenario looks like this. A founder builds an app where users can view their own orders. The URL for viewing an order looks something like /orders/1234. The backend fetches the order with that ID and returns it. The question an access control review asks is: does the backend verify that order 1234 actually belongs to the authenticated user making the request, or does it just fetch and return whatever ID is passed in?

In a vibe coded app, the answer is very often the latter. The AI generated the endpoint to fetch by ID because that’s what makes the feature work. The ownership check, the part that says “and also confirm this record belongs to the person asking for it”, frequently doesn’t make it in. Which means any authenticated user can change the ID in the URL to 1235, 1236, and so on, and read other users’ orders without any resistance at all.

This applies to any resource that has an identifier the user can see or manipulate: orders, invoices, messages, profiles, uploaded files, anything. And the exploit requires no special tooling, just changing a number in a URL or intercepting a request and modifying the ID field.

In one B2B SaaS I audited, cold requests with no token at all returned full dashboard data for multiple client accounts, including pipeline values, account names, and every tracked sales signal. The unauthenticated endpoints were the more dramatic finding, but the IDOR-style access between accounts on the same platform was sitting right underneath it.


The client-side authorization problem

This one shows up across virtually every platform I look at and it’s worth its own mention because it’s structurally different from IDOR even though it produces similar outcomes.

AI tools generate UIs that hide or show elements based on the user’s role or status. Admin buttons don’t appear for regular users. Premium features are greyed out for free tier accounts. This looks like access control and functions like access control in normal use. The problem is that it isn’t access control, it’s just UI logic. The underlying API endpoints that those buttons call are still there and still reachable, and if the server doesn’t independently verify that the requesting user has permission to perform that action, the UI restriction is trivially bypassed by calling the endpoint directly.

In one engagement I came across a subscription bypass where a user could trigger premium functionality by calling the relevant endpoint with a standard account because the only check in place was the UI hiding the button. The server assumed that if the request arrived, it must have come from a legitimate premium user, because how else would they have known to call it.

Vibe-coded route files have a documented pattern of shipping with auth checks written as comments rather than implemented code. The AI generates a placeholder, the app functions correctly in normal use, and the actual enforcement never happens.


What an access control review actually checks

When I run an access control audit on an app, the core questions being answered are fairly consistent across every engagement:

Does every API endpoint verify authentication independently, not just rely on the UI to restrict access? Does every endpoint that returns user-specific data verify that the requesting user owns that data, not just that they’re logged in? Are there role or permission checks enforced server-side for any functionality that’s supposed to be restricted by plan, role, or status? Can a user escalate their own privileges by manipulating a parameter, a header, or a request body? Are there any admin or elevated-privilege endpoints reachable from a standard user account?

The testing methodology is direct rather than theoretical. Two test accounts get created, typically at different privilege levels. Requests made by account A get replayed with the credentials of account B and the responses are observed. Admin endpoints get called from a standard account and the response is checked. Resource IDs get incremented and decremented to see what comes back. If the server returns data or performs actions it shouldn’t, that’s a confirmed finding regardless of what the code is supposed to do.

This is an area where automated scanning genuinely falls short, by the way. IDOR and privilege escalation are what researchers call semantically complex vulnerabilities, meaning they require understanding the intended behavior of the application to identify a violation of it. A scanner can tell you an endpoint exists and returns a 200. It can’t tell you whether the data in that 200 response belongs to the user who asked for it.


Why AI-generated code is particularly exposed here

Access control failures are architectural rather than syntactic. They’re not typos, they’re missing logic, and missing logic doesn’t produce an error that a code generator can self-identify and fix. The app works correctly for the happy path, which is all the AI needs to confirm the feature is functional.

The CSA’s 2026 research note found that privilege escalation paths in AI-generated code weren’t random omissions, they were architecturally valid code with logically excessive permissions. The code does what it was asked to do. What it wasn’t asked to do is restrict that access to the users who are actually supposed to have it.

That gap, between functional code and correctly scoped code, is exactly what an access control review is designed to find.


Back to blog

(function() { function applyMainRole() { var hero = document.getElementById('hero'); if (!hero) return false; var node = hero; while (node.parentElement && node.parentElement !== document.body) { node = node.parentElement; } if (node && node.parentElement === document.body) { node.setAttribute('role', 'main'); return true; } return false; } if (applyMainRole()) return; var attempts = 0; var interval = setInterval(function() { attempts++; if (applyMainRole() || attempts > 20) { clearInterval(interval); } }, 250); })();