AI coding tools are excellent at getting an app to run. They do not know your authorization model unless you explain it and verify the result. A prompt may ask for an invoice endpoint, but it rarely says that a member of Tenant A must never read an invoice from Tenant B.
GitHub's Octoverse 2025 found that Broken Access Control had become the most common CodeQL alert. It appeared in more than 151,000 repositories, up 172% year over year. GitHub pointed to both CI/CD permission mistakes and generated scaffolding that skipped important authorization checks.
The useful news for anyone shipping fast is that the failures cluster. Start with these three patterns.
1. Row-level security left off
The single most damaging bug. When an app talks to Supabase or Firebase directly from the browser, the anon key is public by design. That's fine, if row-level security is enabled and every table has a policy. When RLS is off, anyone can read or write the entire table straight from the client.
# If this returns rows without a session, RLS is not protecting the table
curl "https://YOUR-PROJECT.supabase.co/rest/v1/users" \
-H "apikey: YOUR_ANON_KEY"
The fix is one toggle plus a policy per table:
alter table users enable row level security;
create policy "users read own row"
on users for select
using ( auth.uid() = id );
2. Secrets baked into the frontend bundle
Stripe secret keys, service-account JSON, and third-party API keys get written straight into source that ships to every visitor's browser. Anyone can open dev tools and read them, then run up your bill or take over accounts.
The rule is simple: any secret that must stay secret never touches client code. Move it to a server-only environment variable and call it from an API route. Only truly public values get a NEXT_PUBLIC_ (or VITE_) prefix.
3. Endpoints that check login but not ownership
An endpoint like GET /api/invoice/:id often verifies you're logged in but never checks you actually own invoice :id. So any authenticated user can read anyone's data by changing the number in the URL. This is the most common flaw of all in AI-built CRUD apps.
Every endpoint with a path parameter needs an ownership check:
const invoice = await db.invoice.findUnique({ where: { id } });
if (!invoice || invoice.userId !== session.user.id) {
return new Response("Not found", { status: 404 });
}
The takeaway
Build fast, but before real users touch it, check these three. Enable RLS with a policy on every table, pull every secret out of the bundle, and add an ownership check to every endpoint that takes an ID. That's an afternoon of work, and it's the difference between a demo and a product you can trust with someone's data.
If you'd rather have someone run the check for you, that's part of what we do.
Sources
Want us to check your app for this?
We prove what’s exploitable and hand you a clear report. Share your app and we’ll confirm scope.
Start an audit →