7 Bugs Every AI-Generated Codebase Has
AI coding tools ship working demos fast. They also ship the same seven bugs over and over. Here is what we find every time we audit one, and how to catch them before your users do.

AI coding tools are good at one thing: getting you to a working demo fast. Lovable, Cursor, v0, Bolt. They all do it. You describe an app, you get an app.
The problem starts after the demo. We have audited dozens of AI-built codebases, and the same bugs show up every single time. Not random bugs. The same seven. They share a root cause: the model optimizes for "looks done," not "is correct."
Here is the list, in the order they tend to bite.
#1. The auth check that only runs on the frontend
This is the most common one, and the most dangerous. The login page works. The dashboard redirects you if you are logged out. Looks secure.
Then you open the network tab, copy the API call, and run it with no session cookie. It returns everything.
The model guarded the UI, not the data. The route handler never checks who is asking.
// What the AI wrote
app.get("/api/projects", async (req, res) => {
const projects = await db.query("SELECT * FROM projects");
res.json(projects); // returns every project, for everyone
});The fix is not complicated. It is just missing.
app.get("/api/projects", requireAuth, async (req, res) => {
const projects = await db.query(
"SELECT * FROM projects WHERE owner_id = $1",
[req.user.id]
);
res.json(projects);
});A frontend redirect is a user-experience feature. It is not a security control. The only auth that counts runs on the server, on every request that touches data.
#2. SQL built with string concatenation
The model knows about parameterized queries. It uses them most of the time. Then somewhere in the codebase, usually a search or filter endpoint, it builds a query by gluing strings together.
const q = `SELECT * FROM users WHERE email = '${email}'`;One of those is all it takes. Search every file for backtick-quoted SQL and template literals near a query call. You will usually find at least one.
#3. Secrets committed to the repo
.env files with live keys. API tokens hardcoded in a config file. A Stripe secret sitting in the frontend bundle because it got prefixed with NEXT_PUBLIC_.
The model has no concept of "this value is dangerous in git history." It treats a secret like any other string. Check your commit history, not just your current files.
#4. No rate limiting anywhere
Every endpoint accepts unlimited requests. The login route will take ten thousand password guesses a second. The AI-quote endpoint will run up your model bill if someone scripts it.
AI-generated apps almost never include rate limiting because the demo never needed it. Production does.
#5. Race conditions in "check then write" logic
This one hides until you have traffic. The pattern looks fine:
const existing = await getInvoice(sessionId);
if (!existing) {
await createInvoice(sessionId, amount); // fulfill the order
}Two requests arrive at the same time (a webhook and a success-page call, say). Both read "no invoice." Both create one. Now the customer is charged twice, or fulfilled twice.
The model writes the happy path. It does not reason about two requests interleaving. The fix is a database constraint plus an upsert, not an if statement.
#6. File uploads that trust the client
The upload endpoint checks the file type. It checks it by reading the Content-Type header the browser sent, which the attacker controls. Then it saves the file with its original extension, on the same origin as your app.
That is how you get a .html file with a script in it, served from your own domain, running in your users' sessions. Validate by sniffing the actual bytes, store uploads off the app origin, and never trust a client-provided MIME type.
#7. CSRF tokens that are generated but never checked
The fanciest version of "looks done." The code generates a CSRF token. It puts it in a cookie. It even sends it to the frontend. And then no middleware ever validates it on the way back in.
Generating the token is the easy half. The enforcement, wiring the check into every cookie-authenticated state-changing route, is the half that actually protects you, and it is the half that gets skipped.
#The pattern behind all seven
Look at the list again. Every one of these is a case where the model produced something that looks complete. The auth exists (on the frontend). The token exists (unchecked). The type validation exists (trusting the client).
That is the real risk of AI-generated code. It is not that it writes obvious garbage. It is that it writes convincing, almost-right code that passes a glance and fails under an adversary.
If you built something with an AI tool and you are about to put real users or real money through it, get the seven checked before launch. We do this for founders every week. If you want a second set of eyes, tell us what you built and we will take a look.


