We've reviewed dozens of AI-assisted SaaS codebases. The good ones ship fast. The bad ones also ship fast — and then fall apart six months later when a security audit, a scaling event, or a new hire opens the hood.
After enough of these reviews, you start to see the same patterns. Here are the five that appear most consistently, along with the fix for each.
No Input Validation on API Routes
AI-generated backends tend to trust their input completely. The code works in the happy path, but the sad paths are empty.
export async function POST(req: Request) {
const { email, planId } = await req.json()
await db.createSubscription({ email, planId })
return Response.json({ ok: true })
}import { z } from 'zod'
const schema = z.object({
email: z.string().email(),
planId: z.enum(['starter', 'pro', 'enterprise']),
})
export async function POST(req: Request) {
const result = schema.safeParse(await req.json())
if (!result.success) {
return Response.json(
{ error: result.error.flatten() },
{ status: 400 }
)
}
await db.createSubscription(result.data)
return Response.json({ ok: true })
}One unvalidated string field is a SQL injection vector. One unvalidated enum is a privilege escalation waiting to happen.
Secrets Hardcoded or Stored Adjacent to the Repo
LLMs are trained to be helpful. That means they'll happily accept a hardcoded API key in a prompt and write code that uses it directly. We regularly find:
- Stripe secret keys in .env.example (committed to git)
- API keys in comments explaining what the variable “should be”
- Supabase service role keys hardcoded in a utility file
The fix isn't complicated: rotate whatever was exposed, then store everything in a proper secrets manager or at minimum .env.local (gitignored) with runtime injection in CI/CD. But catching it requires actually auditing the commit history — not just the current file tree.
Missing Error Boundaries in React Apps
React renders are optimistic. Without error boundaries, a single unhandled exception anywhere in the tree unmounts the entire app — your customer sees a blank screen, not an error message.
export default function Dashboard() {
const data = useSomeDataThatMightFail()
return <ComplexUI data={data} />
}import { ErrorBoundary } from 'react-error-boundary'
export default function Dashboard() {
return (
<ErrorBoundary
fallback={
<div>
Something went wrong.{' '}
<a href="/">Reload</a>
</div>
}
>
<ComplexUI />
</ErrorBoundary>
)
}The fix takes ten minutes. The blast radius without it is your entire product going dark.
Zero Test Coverage on Business Logic
AI tools write test files when you ask for them. They don't write them when you don't. The result is coverage that's cosmetic at best: a few passing smoke tests on the happy path, nothing on the edge cases that matter.
The patterns we see most:
- No tests on billing code (refunds, upgrades, dunning)
- No tests on auth flows (token expiry, permission checks)
- Integration tests that mock the database (which defeats the purpose)
The fix isn't “write more tests.” It's identifying which 20% of your code is responsible for 80% of your business risk and starting there.
N+1 Queries from AI-Generated ORM Code
ORMs abstract the database nicely. Too nicely. AI-generated code tends to iterate over a collection and call the database inside the loop without realising it.
const users = await db.user.findMany()
for (const user of users) {
const plan = await db.plan.findUnique({
where: { id: user.planId },
})
console.log(user.name, plan.name)
}const users = await db.user.findMany({
include: { plan: true },
})
for (const user of users) {
console.log(user.name, user.plan.name)
}At ten users, this is invisible. At ten thousand, it crashes your database. We've seen apps go down at their first press mention because of this exact pattern.
What to Do Next
These five patterns are a starting point, not a complete picture. In most AI-generated codebases, they compound — a missing error boundary hides a broken API call that's leaking secrets that aren't rotated because there are no tests to catch the regression.
Free Codebase Review
If any of these patterns look familiar, our free codebase review will find the rest.
We'll map the failure patterns, rank them by risk, and give you a written breakdown — no sales call required.