They're not bugs. They're patterns.
We've reviewed dozens of codebases built with AI tools over the past year. Cursor, Copilot, Claude, ChatGPT. Different tools, different developers, different industries. The code looks different every time, but the security issues? Those are remarkably consistent.
A 2024 study analyzing over 100 large language models found that only 55% of AI-generated code was secure across 80 coding tasks. Newer and larger models didn't generate significantly more secure code than their predecessors.
This isn't because AI tools are bad at security. It's because they optimize for what you ask for: working features. Security is usually an afterthought in the prompt, if it's mentioned at all. "Build a login system" gets you a login system. It might not get you a secure one.
"AI tools optimize for what you ask for: working features. Security is usually an afterthought in the prompt, if it's mentioned at all."
Here are the five vulnerabilities we find in almost every AI-generated codebase, and what to do about them.
1. Broken Access Control
What it looks like: A user can access or modify another user's data by changing an ID in the URL or request body.
Real example: An AI-generated API endpoint for viewing orders:
GET /api/orders/12345
The code checks if the user is logged in. It doesn't check if order 12345 belongs to that user. Any authenticated user can view any order by guessing or iterating through IDs.
"The code checks if someone is logged in. It doesn't check if they should see what they're asking for."
Why AI does this: When you prompt "create an endpoint to view order details," the AI focuses on retrieving and displaying the order. Access control is a separate concern that isn't part of the core request.
The fix: Every endpoint that accesses user data needs an ownership check. Not just "is someone logged in" but "does this specific user have permission to access this specific resource." This should be a consistent pattern across your entire API, not added ad-hoc to each endpoint.
Broken access control has been ranked the #1 web application security risk by OWASP since 2021, with 94% of applications tested showing some form of this vulnerability.
2. Secrets in Code
What it looks like: API keys, database passwords, and other credentials hardcoded in source files or committed to version control.
Real example: We regularly find files like this:
const stripe = require('stripe')('sk_live_abc123...');
const dbPassword = 'production_password_here';
Sometimes the secrets are in environment variable files (.env) that got committed to git. Sometimes they're in config files. Sometimes they're right in the application code.
Why AI does this: AI generates complete, working examples. When you ask for Stripe integration, it shows you how to initialize Stripe with a key. It uses a placeholder, but developers often replace the placeholder with real credentials and forget to externalize them later.
The scale of this problem is staggering: GitGuardian detected 12.8 million secrets leaked on public GitHub repositories in 2023 alone, a 28% increase from the prior year. Even more concerning, 90% of exposed secrets remained active five days after being leaked.
The fix: Audit every file in your repository for hardcoded secrets. Use a tool like git-secrets or truffleHog to scan your git history (secrets removed from current code may still exist in previous commits). Move all credentials to environment variables loaded at runtime, never committed to version control.
3. SQL Injection (Yes, Still)
What it looks like: User input gets concatenated directly into database queries.
Real example: An AI-generated search function:
def search_products(query):
sql = f"SELECT * FROM products WHERE name LIKE '%{query}%'"
return db.execute(sql)
If a user searches for '; DROP TABLE products; --, bad things happen.
Why AI does this: String interpolation is the most readable way to show how a query works. AI tools prioritize clarity in their explanations. Parameterized queries are safer but look more complex, so they're not always the default suggestion.
The fix: Every database query that includes user input must use parameterized queries or an ORM that handles escaping. This is non-negotiable. Search your codebase for string concatenation or interpolation anywhere near database operations.
4. Missing Rate Limiting
What it looks like: APIs that can be called unlimited times with no throttling.
Real example: A password reset endpoint that sends emails:
POST /api/reset-password
{ "email": "[email protected]" }
Nothing stops an attacker from calling this endpoint 10,000 times, either to spam a user's inbox or to enumerate which email addresses exist in your system.
Why AI does this: Rate limiting is infrastructure-level code that isn't part of the feature being built. When you ask for a password reset feature, you get the password reset logic. Rate limiting is assumed to exist somewhere else.
The fix: Implement rate limiting at the API gateway or application level. Start with authentication endpoints (login, registration, password reset) and any endpoint that sends emails, texts, or notifications. Then expand to all public endpoints.
5. Verbose Error Messages
What it looks like: Error responses that reveal internal details about your system.
Real example: A failed login attempt returns:
{
"error": "User not found in database",
"query": "SELECT * FROM users WHERE email = '[email protected]'",
"stack": "Error at AuthController.login (line 47)..."
}
Or subtly different messages for "user doesn't exist" vs "wrong password," allowing attackers to enumerate valid accounts.
Why AI does this: Detailed error messages are helpful during development. AI generates code that's easy to debug. The production concern of information disclosure isn't part of the prompt.
The fix: Create a consistent error handling pattern that logs detailed errors server-side but returns generic messages to clients. Authentication endpoints should return the same message whether the user doesn't exist or the password is wrong.
The Pattern Behind the Patterns
Notice what these all have in common: they're not mistakes in the literal sense. The code does what it was asked to do. The login works. The search works. The API endpoints return data.
"The problem is what the code wasn't asked to do. Check ownership. Protect secrets. Sanitize input. These are the concerns experienced developers add automatically."
The problem is what the code wasn't asked to do. Check ownership. Protect secrets. Sanitize input. Limit abuse. Hide internals. These are the concerns that experienced developers add automatically, not because they're explicitly requested, but because they know what happens when you don't.
AI tools are getting better at this. But they're fundamentally responding to prompts, and most prompts are about features, not security. Until that changes, these patterns will keep appearing.
What To Do About It
If you've built with AI tools, assume these issues exist in your codebase. Not because you did something wrong, but because this is how the tools work right now.
The good news: these are known patterns with known fixes. A security-focused review can identify the specific instances in your codebase and prioritize them by risk.
We offer a free 30-minute architecture review with our CTO, Dan Green. Share your product demo, tech stack, and any docs or code you're comfortable with. Dan will ask targeted questions and give you an honest assessment. Within 48 hours, you get a written summary with the highest-priority issues and recommendations.
No charge, no obligation. MNDA available on request.
Book your free architecture review →
ulta.io helps companies secure and scale AI-generated code. We've seen these patterns enough times to fix them quickly, so you can get back to building your product.