TL;DR. We run a lot of audits on apps built with Cursor, Claude Code, Bolt, Lovable, and long ChatGPT sessions. The codebases differ wildly, but the findings almost never do. Eleven problems show up again and again, roughly in the order they tend to hurt: hardcoded secrets and credentials, no input validation (the injection surface), authentication that checks the box but not the request, zero test coverage, no error handling on the unhappy path, N+1 queries and performance left to chance, stale dependencies with known CVEs sitting unpatched, duplicated variables and functions, no consistent architecture, complex async state collapsed into callback hell instead of streams, and no awareness of the deployment environment. None of these are exotic. All of them are predictable — and all of them are fixable without a rewrite. This is the engineering companion to our founder's guide to shipping an AI prototype; if you want it handled, that is what an AI code audit does.
What an Audit Is — and Isn't
An audit is not a rewrite, and it is not a verdict on whether you should have used AI to build the thing. It is a structured read of a working codebase that answers one question: what happens the first time this meets a real attacker, a real spike in traffic, or a real change six months from now? The findings below are not hypothetical categories from a checklist — every one of them is something we have found, redacted, and fixed in a real engagement. We reference our sample audit report and the AI code audit service throughout, because that is where the fix actually happens; this post is the evidence for why the fix is needed.
Why the Findings Repeat
AI coding tools optimize for one thing: the shortest path to code that runs. That is genuinely useful — you get an idea into a working state in hours. But "runs in the demo" and "maintainable and safe in production" are different targets, and the gap between them is remarkably consistent across projects.
The reason is structural. A model generating code has a narrow window of context and no memory of the decisions it made three files ago. It cannot test the thing it just wrote, it has no sense of your runway or your security posture, and it has no incentive to keep the codebase coherent over time. So it makes the locally optimal choice every time — and the sum of locally optimal choices is a codebase that works today and resists every change tomorrow.
After enough audits, the failures cluster into the same eleven buckets. Here they are.
1. Hardcoded Secrets and Credentials
Every audit finds this, and it is exactly the smell that costs real money: .env files committed to the repository, API keys and database credentials hardcoded directly in source, third-party tokens baked into client bundles that ship to every browser that loads the app. One leaked OpenAI or Stripe key can run up thousands of dollars in unauthorized charges within hours — or hand an attacker your data store outright.
Why AI does this. Hardcoding a key works immediately; wiring up a secrets manager or environment injection does not, and the model has no reason to prefer the slower path when the faster one also "runs." The shortest path to a working feature is almost never the secure one.
How we fix it. Secrets come out of source and into proper environment management. Anything that was ever committed to git is treated as already compromised and rotated, not just removed — a git rm without rotation leaves the old key valid in every clone and every commit history. This is the non-negotiable part of any audit: a leaked key is an emergency, and it gets fixed first.
2. No Input Validation — the Injection Surface
SQL injection, XSS, and prompt injection are pervasive in AI-built code, and the root cause is the same one every time: user input goes straight into a query, a template, or an LLM prompt with no validation or sanitization in between. A single vulnerable endpoint can compromise your entire database or let an attacker manipulate what your own AI features do.
Why AI does this. Validation is a second request the model was never asked to make. It generates the code that satisfies the happy-path prompt — "take the user's message and save it" — and the happy path never mentions what to reject.
How we fix it. Every boundary where untrusted input enters the system gets explicit validation and parameterized queries or templates, not string concatenation. For AI features specifically, that means treating the prompt construction itself as an injection-prone boundary, not just the database layer.
3. Auth That Checks the Box, Not the Request
AI-generated apps often implement authentication at the surface level — a login screen exists, and it works — but the backend never actually verifies permissions per request. API endpoints accept anything that hits them. Admin routes are reachable without a role check. User A can see User B's data by changing an ID in the URL — an insecure direct object reference sitting in production.
Why AI does this. "Add a login page" and "check that this specific request is allowed to touch this specific record" are different problems, and the model solves the one it was asked about. Authentication is visible in a demo; authorization gaps are invisible until someone exploits them.
How we fix it. We audit every endpoint against who is actually allowed to call it, not who the login screen implies is allowed, and add the per-request authorization checks that were missing — ownership checks on records, role checks on admin routes, and rate limiting on anything a script could hammer.
4. No Tests — Every Deployment Is a Gamble
The single most common finding: there are no tests at all. Not a thin suite, not flaky tests — zero. The app was validated by clicking through it, and that is the entire safety net.
This is invisible right up until the moment it is catastrophic. With no tests, there is no way to know whether a change broke something other than shipping it and waiting for a user to complain. Every deployment becomes a manual regression pass that nobody actually performs, so refactoring becomes terrifying, dependency updates get skipped, and the codebase calcifies — not because the code is bad, but because no one dares touch it.
Why AI does this. Generating a feature and generating tests for that feature are two separate requests, and nobody made the second one. The model will happily write tests if asked, but left to its own devices it ships the happy path and stops.
How we fix it. We do not aim for 100% coverage on day one. We add a thin layer where it pays off most: a smoke test that the app boots, tests around the money-handling and auth logic, and a regression test for every bug we fix during the audit. That alone turns deployments from a gamble into a routine.
5. No Error Handling on the Unhappy Path
The app works exactly as demoed — as long as the network never drops, the third-party API never times out, and the user never does anything unexpected. The moment one of those things happens, the symptoms are ugly: an unhandled promise rejection crashes the whole request, a failed API call leaves the UI stuck on a spinner forever, an exception surfaces a raw stack trace to the user instead of a message that means anything to them.
Why AI does this. The happy path is what the prompt described and what the demo exercised. Error handling is defensive code written for situations the model was never told to imagine, and it adds lines without making the demo look any more impressive — so it is the first thing skipped under an implicit time budget.
How we fix it. We walk every external call — API, database, file system — and add the failure branch: retries with backoff where retrying helps, a fallback or a clear error state where it does not, and logging that tells you what actually happened instead of a generic "something went wrong." The goal is that a third-party outage degrades your app gracefully instead of taking it down.
6. N+1 Queries and Performance Left to Chance
The list screen that loads instantly with ten rows in development grinds to a crawl with ten thousand in production. The classic cause is an N+1 query: one query to fetch a list, then a separate query per row to fetch its related data, so a screen that should cost one round trip to the database costs hundreds. Missing indexes, unbounded result sets with no pagination, and loading entire objects when only a field or two is displayed are the usual companions. Our guide to databases and indexes covers the mechanics of why this is slow and what a healthy query plan looks like.
Why AI does this. The N+1 pattern is the most obvious way to write the loop, and it produces correct output — the model has no feedback loop that tells it the query count matters until someone measures it under real data volume, which a demo with a handful of rows never does.
How we fix it. We profile the actual query patterns under realistic data volume, collapse the N+1 chains into joins or batched loads, add the missing indexes, and put pagination or limits on anything that returns an unbounded set. This is usually the single highest-leverage performance fix in an audit, because one bad list screen can account for most of a page's load time.
7. Dependency and CVE Drift
npm audit or its equivalent turns up a wall of known vulnerabilities the moment anyone runs it — because nobody had. Packages are pinned to whatever version was current when the AI tool scaffolded the project, transitive dependencies nobody chose directly carry their own CVEs, and there is no process for finding out when a patch ships.
Why AI does this. The model picks a package that solves the immediate problem and moves on; it has no ongoing relationship with your project that would prompt it to revisit that choice later. Dependency hygiene is a maintenance activity, and nothing about generating a feature triggers maintenance.
How we fix it. We run a dependency vulnerability scan, patch or replace anything with a known exploit, and set up a process — even a simple scheduled scan — so this does not silently drift again the moment the audit ends.
8. Duplicated Variables and Functions
Open an AI-built codebase and search for the same date-formatting helper. You will often find it three or four times — slightly different each time, because each was generated in isolation for the screen that needed it. The same goes for validation rules, API clients, currency math, and configuration constants.
Duplication is not just ugly; it is a correctness time bomb. When the logic needs to change — a new tax rule, a fixed rounding bug, an updated endpoint — you have to find every copy. You will miss one. Now two parts of the app disagree about something they should agree on, and that disagreement is the next production incident.
Why AI does this. The model rarely searches the existing codebase for a helper it could reuse. It is cheaper, from its perspective, to regenerate the function inline than to discover and import the one that already exists. Each generation is locally reasonable; the aggregate is drift.
How we fix it. We find the clusters of near-identical code, extract a single source of truth, and route every call site through it. This is one of the highest-leverage cleanups in most audits: it shrinks the codebase and removes whole categories of "fixed here but not there" bugs.
9. No Consistent Architecture
This one is jarring to see for the first time. Two screens in the same project will be written as if by two different teams: one fetches data in the component, the other through a service layer; one holds state one way, the next does it completely differently; naming, folder structure, and error handling change from feature to feature. There is no spine.
A codebase with no consistent architecture is one where every file you open is a surprise. Onboarding a developer takes weeks because there is no pattern to learn — only a hundred special cases to memorize. Worse, when patterns conflict, the seams between them are exactly where bugs breed.
Why AI does this. The model has no persistent picture of "how this app is built." Each prompt is a fresh start, so it reaches for whatever pattern fits that one request. Over a project's life that produces a patchwork — every piece sensible alone, the whole thing incoherent.
How we fix it. We pick one architecture that fits the project — not a dogmatic one, a fitting one — and converge the codebase onto it incrementally, so a developer who learns one feature can predict how the next one works.
10. Stream-Based State Avoided — Straight Into Callback Hell
This is the most technically interesting failure, and the one that quietly breaks the hardest features. AI-generated code tends to avoid stream- and reactive-state models in favor of imperative callbacks. Instead of modeling "this value changes over time and the UI reacts," it wires up a callback, which triggers another callback, which sets a flag, which fires a third — and the result is callback hell.
For simple screens you barely notice. But the moment the state is genuinely complex — a multi-step form with cross-field validation, a live-updating dashboard, anything with debouncing, retries, cancellation, or optimistic updates — the callback approach falls apart. The classic symptom is the form that almost works: it validates, but the error clears at the wrong moment; it submits, but a double-tap fires it twice.
Why AI does this. Imperative callbacks are the most common pattern in its training data and the easiest to generate one piece at a time. Reactive and stream-based models require holding the whole state machine in mind at once — exactly what a context-limited generator is worst at.
How we fix it. We identify the complex-state features and rebuild their state layer properly — as streams or a reactive state model appropriate to the stack — so the UI is a function of state rather than a pile of callbacks racing each other.
11. No Awareness of the Deployment Environment
The model writes code as if it will run as a single process on one machine — because from inside the prompt, that is the only environment it can see. It has no idea how many instances will run, what managed services already exist, or how traffic is routed. So it defaults to the simplest possible topology, and that default quietly breaks the moment the app is deployed for real.
The symptoms are always the same. State that lives in process memory — a cache, sessions, rate-limit counters — works perfectly on one instance and silently diverges the moment a second replica comes up behind the load balancer. Background jobs fire on every instance instead of once, so the email goes out three times.
Why AI does this. It has no picture of your infrastructure. It does not know you already have Redis, a message queue, and object storage — so it reimplements them in memory. The deployment topology is exactly the context a prompt cannot contain.
How we fix it. We map the actual deployment and move shared state to where it belongs: cache, sessions, and locks into Redis or the database, files into object storage, recurring work onto a real scheduler or queue. The result is code that scales horizontally.
How We Find Them
Every finding above starts with an automated pass and ends with a human reading the code. The automation — static analysis, dependency and secrets scanning, API cost profiling — is what makes an AI-accelerated audit fast: it clears the categories that are mechanical to detect (findings 1, 4, 6, and 7 above surface here almost immediately) so the time our engineers spend is concentrated on the categories that require judgment — authorization logic, architecture, and whether a given error path actually matters for your product. Neither half works alone: automation alone misses everything that requires understanding what the code is for, and manual review alone does not scale to a real codebase in a week. The full breakdown of the process is on the AI code audit service page.
What You Get in the Report
Findings do not arrive as a raw list. Every one is ranked by severity — critical, high, medium, low — with a plain-language explanation of the risk, proof of concept where it applies, and a specific fix recommendation, the same shape every finding above followed. If you want to see the format before committing to anything, request a redacted sample audit report — the same report structure a real engagement produces, with client-identifying details removed.
The Pattern Behind the Pattern
Step back and the eleven findings share one root cause: AI optimizes each generation locally, and nobody is optimizing the codebase globally. Secrets management, input validation, authorization, tests, error handling, query performance, dependency hygiene, deduplication, architecture, state modeling, and deployment awareness are all whole-system properties. They cannot emerge one prompt at a time, because no single prompt can see the whole. That is precisely the gap a human review closes.
The reassuring part is that none of this means the AI-built foundation is wasted. The features work; the product is real. What is missing is the connective tissue — and adding it is far faster than rebuilding from scratch.
Frequently Asked Questions
Get the Findings for Your Codebase
If you have an AI-built app and you recognize any of these eleven, you are not behind — you are exactly where almost every AI-generated codebase lands. The fix is not a rewrite; it is a focused audit that adds the connective tissue the AI could not.
Run your codebase through an AI code audit, request a sample audit report to see the format first, or book a free assessment and we will tell you which of the eleven is your biggest risk, what it takes to fix, and give you a fixed-scope quote — not a guess.

