Security flaws in AI-generated code: the 6 most common mistakes
AI tools write code that works today. Whether it is also secure is a different question. For a large study, Veracode tested code from more than 100 language models: in 45 percent of cases it contained flaws from the OWASP Top 10, the most common vulnerabilities on the web (Veracode, 2025). Notably, newer and larger models did no better than older ones.
The problem isn't that AI writes bad code. The problem is that it does what you tell it, and rarely what you forgot to tell it. Here are six flaws that come up especially often in AI-generated projects, each with an example and a fix.
In short
- According to Veracode, AI-generated code contained known security flaws in 45 percent of tests.
- The most common mistakes: prices from the browser, missing access checks, databases without row level security, secrets in the frontend, personal data in logs and unvalidated input.
- You can find the worst gaps yourself with a 30-minute check.
1. The browser sets the price
A classic in checkouts: the amount comes from the browser's request instead of being read from the product on the server.
// Insecure: the amount comes from the browser
const { productId, amount } = await req.json();
await stripe.checkout.sessions.create({
mode: "payment",
line_items: [{
quantity: 1,
price_data: {
currency: "eur",
unit_amount: amount,
product_data: { name: productId },
},
}],
});
Anyone who edits the request pays one cent. The fix: the server looks up the price itself.
// Secure: the price comes from the server
const { productId } = await req.json();
const unitAmount = await priceFor(productId);
The rule behind it applies to more than prices: everything that comes from the browser is a suggestion, not a fact. That includes roles, discounts, quantities and user IDs.
2. API routes without access checks
The interface only shows a button to signed-in users, but the route behind it checks nothing. Anyone who knows the address can call it directly.
// Insecure: no check who is asking
export async function POST(req) {
const { orderId } = await req.json();
return Response.json(await db.order.delete(orderId));
}
The fix: every route checks for itself whether there is a valid session and whether this person may change this exact record.
export async function POST(req) {
const user = await requireUser(req);
const { orderId } = await req.json();
const order = await db.order.find(orderId);
if (order.ownerId !== user.id) {
return new Response("Forbidden", { status: 403 });
}
return Response.json(await db.order.delete(orderId));
}
3. A database without access rules
Many AI tools build on Supabase or Firebase. There, the browser accesses the database directly, protected only by access rules. In Supabase they are called row level security. Without them, anyone with the public key can read entire tables.
That is exactly what happened to many Lovable apps in 2025: user data, API keys and payment data were readable by outsiders (CVE-2025-48757).
The fix: enable row level security for every table and write policies that define who may read and change which rows. Then test in a signed-out browser that nothing gets through anymore.
4. Secrets in the frontend
API keys for payments, AI services or email delivery end up in code that is shipped to the browser. Anyone can find them in the source and use them at your expense.
Typical places: environment variables prefixed with NEXT_PUBLIC_ or VITE_, the Supabase service role key in the client, or keys hard-coded in the source.
The fix: secret keys belong on the server only. Only what may be public goes to the browser. And a key that has been public once doesn't get hidden, it gets replaced.
5. Personal data in logs
For debugging, AI likes to log everything: email addresses, names, sometimes entire requests.
console.log("checkout", email);
Logs travel to hosting providers, monitoring services and error trackers. That means you process personal data in places nobody has on their radar, not even in the privacy policy. The fix: only log what debugging needs, for example an order number instead of the email address.
6. Unvalidated input
What users type ends up unfiltered in the database or on the page. The result is cross-site scripting and injection attacks. According to Veracode, the tested models failed to defend against cross-site scripting in 86 percent of relevant cases (Veracode, 2025).
Missing limits belong here too: a form without rate limiting can be submitted thousands of times a minute, a login without limits invites people to try passwords until one works.
The fix: validate input on the server, escape output, use parameterised database queries and rate-limit sensitive endpoints.
How to check your app in 30 minutes
You don't need to be a security expert to find the worst gaps:
- Open the browser tools: look at the network tab to see what your app sends. Do prices, roles or IDs show up that you could change?
- Test while signed out: call API addresses in a private window without logging in. Does data come back?
- Search the source: search the shipped JavaScript for "key", "secret" and "token".
- Check the database rules: is row level security enabled on every table, with matching policies?
- Look at the logs: do email addresses, names or payment data appear there?
If you find something, that's no reason to panic. But it is a reason to act before the next user arrives.
Why tools alone aren't enough
Automated scanners find some of these flaws. Many gaps, however, are logic errors: the route works perfectly from a technical point of view, it just checks the wrong thing. That takes someone who understands what the app should do and asks specifically what it must not do.
This is how I work with my AI crew: Vera checks the code for exactly these patterns, in payments, privacy and login. I assess every finding myself before anything gets fixed or goes live. Machine thoroughness and human judgement together find more than either does alone.
Frequently asked questions
Is AI-generated code insecure?
Not inherently, but often. In a study by Veracode, AI-generated code contained OWASP Top 10 flaws in 45 percent of tests. Larger models did no better.
What is row level security?
Row level security means access rules directly in the database, for example in Supabase. They define which users may read or change which rows. Without them, anyone with the public key can read data.
How do I check my app for security flaws?
Check what the app sends to the server, call endpoints without logging in, search the shipped code for keys, review the database rules and look at the logs. For products with real users, a review by an experienced person is also worth it.
Conclusion
AI-generated code isn't insecure because AI programs badly, but because security rarely makes it into the prompt. If you know the six typical flaws, you'll find most of them in half an hour. If you run a product with real users and real money, you should still have someone look at it who knows what to look for.
