AI Remediation
The Security Holes AI Coding Tools Leave in Your Auth
July 5, 2026 · 9 min read · Anju Kumari
Here is the uncomfortable thing about AI-generated auth: it demos perfectly. You sign up, you log in, you see your dashboard, you log out. Every path you test works, because every path you test is the path you described in the prompt.
Security is what happens off that path. It is the request nobody in your demo ever sends: the login form submitted 10,000 times, the API called with someone else's user ID. Lovable, Replit, Cursor, Bolt, v0 — all of them optimize for the happy path, because the happy path is what the prompt describes and what the founder tests. An attacker is, by definition, someone who refuses to follow it.
This isn't a claim that AI tools write uniquely bad code. It's about mechanism: when code is generated to satisfy a description of what should happen, the handling of what shouldn't happen is exactly the part that gets skipped. Auth is where that hurts most, because auth's entire job is handling hostile input.
Below are the auth failure patterns we check for in every audit of a vibe-coded app: why the generation process produces each one, what an attacker does with it, and — where feasible — a check you can run yourself in five minutes with no engineering background.
The server trusts whatever the browser says
The most common and most dangerous pattern. Your app has a frontend (code running in the visitor's browser) and a backend (code running on your server). The frontend sends requests like "show me the invoices for user 4482." The question is whether the backend verifies the person asking is actually user 4482 — or just takes the request's word for it.
AI tools generate the second version constantly: the prompt said "users can see their invoices," the generated code fetches invoices by whatever ID the frontend passes, and the demo works. Verifying identity server-side was never in the description, so it was never in the code. This hole is called broken object-level authorization, and it tops OWASP's API security risk list — the industry's standard ranking of what actually gets exploited.
What an attacker does: opens your app, watches the network requests, changes user_id=4482 to user_id=4483, and reads someone else's data. Then writes a script that loops through every ID and downloads your customer database. No password cracking required.
Five-minute check: log in, open your browser's developer tools (right-click, Inspect, Network tab), click around, and look at the request URLs. If you see your user ID or record IDs in them, edit one to a different number and resend it. Someone else's data instead of an error means a critical hole — in our audit reports, an automatic CRITICAL on the scale we rate every finding with: CRITICAL, HIGH, and SOLID for the things that are genuinely fine.
Role checks that only exist in the UI
Your app has admins and regular users, and the admin dashboard is hidden from regular users — the button simply doesn't render. The question is whether the server also refuses admin actions from non-admins, or whether hiding the button was the whole defense.
Why AI generates it: "only admins can see the admin panel" is a statement about what's visible, and the tool satisfies it visually. Hiding a component is one line; enforcing the rule on every backend route the panel calls is a change across many files, and nothing in the prompt demanded it. The demo — where you, the admin, click the visible button — passes.
What an attacker does: ignores your UI entirely. The admin panel calls endpoints like /api/admin/users/delete, and those exist whether or not a button points at them. An attacker who finds them (they're visible in your frontend's JavaScript, which every visitor downloads) calls them directly. If the server doesn't re-check the role, they're an admin now.
There's no easy self-check here, but a useful proxy: ask whoever maintains the app to show you where the backend checks the user's role — not where the frontend hides the menu. If the answer involves "the button isn't shown," that is the finding.
Sessions that never expire
When you log in, the app gives your browser a session — a credential proving you're you — so you don't retype your password on every click. Sessions are supposed to die: after inactivity, after a fixed lifetime, and immediately when you log out or change your password.
AI-generated code routinely mints sessions that live forever, and often implements "log out" as the browser throwing its copy away while the server still honors the credential if anyone else has it. The mechanism: expiry and revocation are invisible in a demo. A session that lasts forever behaves identically to a correct one for the first hour, so nothing in the build-test-ship loop ever surfaces the problem.
What an attacker does: any stolen session — from a shared laptop, an old device, one of the other holes on this page — works indefinitely. Resetting the password doesn't help, because old sessions were never invalidated. The victim can't lock the attacker out even after they know something is wrong.
Five-minute check: log in on two browsers. In browser one, change your password (or log out). In browser two, refresh. If browser two is still logged in, sessions aren't being revoked server-side.
Tokens in localStorage
A technical-sounding one, but the plain version is short. The browser has two places to keep your session credential: a properly flagged cookie, which the page's JavaScript cannot read, or localStorage, a general storage bin that any JavaScript on the page can read.
AI tools reach for localStorage because it's the simplest code that makes the demo work — one line, and countless tutorials in the training data do it that way. The result: if any script on your page is compromised — a poisoned third-party chat widget, any cross-site scripting bug anywhere in the app — it can read the token and send it to an attacker, who is now logged in as your user.
Five-minute check: log in, open developer tools, go to the Application tab (Chrome) or Storage tab (Firefox), and look under Local Storage for your site. If you see anything named token, jwt, access_token, or similar, your session credential is sitting in the readable bin.
Password reset that leaks who has an account
Type an email into the "forgot password" form. If the app answers "no account found" for strangers and "reset link sent" for customers, the form is an oracle: anyone can test any email address against your user list.
The mechanism is almost poignant: the AI is being helpful. A specific error message reads as better UX than a vague one, and the prompt said nothing about enumeration. The correct behavior — "if an account exists, we've sent a link," same response either way — looks like worse UX, so it never gets generated unless someone asks.
What an attacker does: runs a leaked email list through your form to find your customers, then phishes exactly those people. For anything health-related or financial, merely confirming someone is a customer is itself a privacy breach.
Five-minute check: the easiest on this list. Submit a real customer email and a made-up one to your own forgot-password form. If the responses differ, it leaks. Check signup too — "email already registered" is the same leak from the other door.
Missing rate limiting on login
Rate limiting means the server notices repetition — the same visitor hammering the same endpoint — and slows or blocks it. Your login form needs it, because without it an attacker can try passwords as fast as your server responds.
AI tools rarely add rate limiting unprompted, and this is the purest case of the happy-path problem: rate limiting defends against behavior that never occurs in a demo. One founder testing one login can't tell a protected form from an open one.
What an attacker does: credential stuffing — replaying email-password pairs from other sites' breaches against your login, betting that people reuse passwords (they do). An unprotected form lets them run at full speed, and your first sign of trouble is compromised accounts.
Five-minute check: try logging into your own account with a wrong password fifteen times in a row. If nothing changes — no delay, no lockout, no "try again later" — there's nothing there.
Webhooks that accept anything
If your app takes payments, you almost certainly have webhooks: URLs where outside services like Stripe send your server messages such as "this customer's payment succeeded." The question is whether your server verifies the message actually came from Stripe — via a cryptographic signature, a tamper-proof stamp the sender includes — or believes anyone who knocks.
AI tools generate the believing version: the prompt said "handle the Stripe payment webhook," the generated handler parses the message and updates the database, and the test — a real event from Stripe's dashboard — passes. Signature verification defends against forged senders, and no forged sender ever appears in a demo. The check is skipped because nothing visible breaks without it.
What an attacker does: finds your webhook URL (guessable — /api/webhooks/stripe) and sends a forged "payment succeeded" event with their own account ID. Your server grants them the paid plan. They never paid. Variants apply to any webhook: forged signups, forged status updates, forged anything.
No five-minute founder check exists here — it requires reading the handler code. It's a standard item on our audit checklist for exactly that reason.
The patterns at a glance
| Pattern | Severity | Can you check it yourself? |
|---|---|---|
| Server trusts whatever the browser says | Critical | Yes — edit an ID in dev tools |
| Role checks only in the UI | Critical | Partially — needs a code question |
| Webhooks that accept anything | Critical | No — requires reading the handler |
| Sessions that never expire | High | Yes — two-browser test |
| Tokens in localStorage | High | Yes — look in dev tools storage |
| Missing rate limiting on login | High | Yes — fifteen wrong passwords |
| Password reset leaks accounts | High | Yes — two-email test |
What to do with this
If a check above failed, don't panic — and don't conclude the AI tools cheated you. They did what they're built to do: produce working software from a description of the happy path. Auth is simply the part of an app where the happy path is the least interesting part, and it's one slice of what breaks when AI builds your app.
The self-checks catch maybe half of these patterns. The other half — authorization logic, webhook verification, how sessions are issued server-side — lives in code, and someone has to read it. That is what our fixed-price code audit is for: a senior engineer reads your AI-generated codebase and delivers a written report covering every pattern on this page, plus architecture, performance, and a production-readiness roadmap — each finding rated CRITICAL, HIGH, or SOLID so you know what's urgent and what's genuinely fine. From $995, report in 5 business days, and the hardening work that follows is quoted fixed and paid on approval.
Run the five-minute checks first. They're free, and they'll tell you whether you need us this week or just eventually.
Related: the full production-readiness checklist · book a vibe coding audit
Talk to us about your project
Senior engineers, honest scoping, fixed prices — quoted before work starts, paid when you approve the result.
Start a conversation →