Broken access control: the #1 vibe-coding vulnerability
Security researchers tracking public vibe-coding incidents through 2026 keep landing on the same root cause behind the most damaging ones: not a missing password, not a leaked API key, but a request the server should have rejected and didn't.
"Who are you?" AI coding tools are generally good at wiring up login, signup, and session cookies — the demo has to have a working login screen, so the assistant builds one.
"Are you allowed to do this specific thing, to this specific record, right now?" This check is a separate piece of logic on every single route — and it's the one AI assistants skip, because the app "works" without it.
Two incidents that made this concrete
The Tea app — a platform built for women to anonymously share safety information about people they were dating — became one of the most widely discussed vibe-coding incidents when private messages between users were exposed to strangers. The root cause wasn't a leaked database credential or a cracked password. It was access-control logic that never actually verified a request for one user's messages was coming from that user.
A second incident, widely referenced in security write-ups of AI-built apps getting compromised, involved Moltbook — where an admin route was reachable without any authentication at all. Not a bug in how the admin panel checked permissions; there was no check to bypass in the first place. The route existed, worked, and answered any request that hit it.
Neither of these needed a sophisticated exploit. Both needed only a URL and a request — which is precisely why broken access control keeps showing up as the leading category behind public vibe-coding breaches: it doesn't require finding a clever bug, it requires noticing that a check nobody wrote is a check that never runs.
Why AI coding assistants ship this by default
Ask an assistant to "add an orders page" and it will, correctly, build a route that fetches an order by ID and displays it. The demo works: log in, view your order, done. What almost never happens automatically is the assistant also writing "and reject this request if the order's owner isn't the currently logged-in user" — because nothing in that prompt asked for it, and the feature appears to work perfectly without it. The gap is invisible until a second account, with a different set of orders, requests someone else's order ID.
The same gap shows up at the UI layer even more often. An assistant asked to "hide the admin panel from regular users" will frequently solve it by not showing a link to /admin in the navigation for non-admins — a client-side, cosmetic fix that does nothing to stop a direct request to that URL from anyone who simply types it in. The page looks protected. It isn't.
The three shapes this takes
Insecure Direct Object Reference (IDOR). A route like /api/orders/1042 returns whatever order has ID 1042, with no check that the requester owns it. Change the number, get someone else's order, invoice, or medical record. This is the single most common pattern — see our dedicated guide on sequential-ID IDOR for exactly how to spot and fix it, including the version that still applies once you've switched to UUIDs.
Unprotected admin or internal routes. A route exists for internal or administrative use, is never linked anywhere a regular user would see it, and is treated as effectively private because of that. It isn't private — it's unlisted, which is a completely different thing. Anyone who guesses, scans for, or finds the route in a leaked source map can reach it directly.
Client-side-only role checks. The app checks if (user.role === 'admin') somewhere in a React component to decide what to render — and nowhere on the server. The check is real code, it just runs in the wrong place: on a machine the attacker fully controls, deciding what to show, not what to allow.
How to actually test for it
The only reliable test is the one almost nobody runs before launch: create a second, low-privilege account, and try to access data that belongs to the first one. Log in as User B, take an ID that belongs to User A — an order, a document, a message thread — and request it directly. If it loads, the access control is broken, regardless of how polished the UI looks for the account you normally test with.
Do the same for anything that looks like an admin or internal route, signed out entirely: no session, no cookie, just the raw URL. A route that responds with real data instead of a 401 or 403 is exposed to anyone on the internet who finds it, whether they found it by guessing, by reading your client-side JavaScript bundle, or by requesting your Wayback Machine history for old, cached versions of the app.
The fix
Every route that touches a specific record needs a server-side ownership check before it does anything else — not a client-side redirect, not a hidden navigation link, an actual check inside the API handler:
// Server-side ownership check — not a UI decision
export async function GET(req: Request, { params }: { params: { id: string } }) {
const session = await getSession(req);
if (!session) return new Response("Unauthorized", { status: 401 });
const order = await db.orders.findUnique({ where: { id: params.id } });
if (!order) return new Response("Not found", { status: 404 });
// The check that vibe-coded apps skip:
if (order.userId !== session.userId && !session.isAdmin) {
return new Response("Forbidden", { status: 403 });
}
return Response.json(order);
}For admin and internal routes, add real authorization middleware that runs before the route handler, not a navigation-level "don't show the link" workaround. If a route exists that only staff should reach, the server needs to say so on every single request to it — obscurity is not a control.
Is your app's authorization actually enforced?
StackSecured's Authorization engine tests ID-bearing routes and discovered admin routes for exactly this — real requests, not a source-code guess — once you've verified you own the domain.
Run a free scanCommon questions
What is the difference between broken access control and broken authentication?
+
Authentication answers 'who are you' — logging in, verifying a password, issuing a session. Access control answers 'what are you allowed to do now that we know who you are.' A vibe-coded app can have a perfectly working login system and still have broken access control, because the login flow and the authorization check on each individual route are two separate pieces of logic, and AI coding tools very often build the first without the second.
Is this the same thing as IDOR?
+
IDOR (Insecure Direct Object Reference) is one specific, very common shape of broken access control — where an ID in a URL or request body is trusted without checking that the current user actually owns that ID. Broken access control is the broader category: IDOR, missing checks on admin-only routes, and client-side-only authorization all fall under it.
Why doesn't manual testing catch this before launch?
+
Because the app works completely normally for the person who built it. Every button, every page, every API call succeeds — because the developer is always testing as themselves, usually as the first user or an admin account. The bug only shows up when a second, lower-privileged account tries to access something that belongs to a different user, which is a test almost nobody runs manually before shipping.
Does StackSecured actually test for this, or just check for the pattern?
+
StackSecured's Authorization (BOLA/IDOR) engine sends real requests: it tests ID-bearing routes it discovers (like /api/orders/123) and any admin routes it finds, checking whether they respond to an unauthenticated or lower-privileged request instead of correctly rejecting it. It's a verified-only engine — it only runs once you've proven you own the domain, since it's making real requests against live endpoints, not just reading source code.
More reading: IDOR: the sequential-ID bug · Vibe-coding security statistics 2026 · Full vibe-coding security checklist