AI-generated APIs almost never include rate limiting
Par Seaworthy · 8 juin 2026
Photo by Scott Rodgerson on Unsplash
The scaffolding is almost always incomplete
Ask any AI assistant to generate a REST API and you will get something that works. Routes, handlers, a database connection, maybe even a Swagger doc. What you almost certainly will not get is any protection against a client hammering the same endpoint a thousand times a minute.
I have been watching this for a while now. ChatGPT, GitHub Copilot, Cursor: they all do it. The generated code is clean, it passes the happy-path demo, and it has no rate limiting at all. Not on the login route. Not anywhere.
This is not a minor omission.
Why AI tools skip it
The reason is not mysterious. Rate limiting does not come up in training examples because tutorials do not need it. A "build a REST API in 10 minutes" article does not include the part where someone tries to log in 50,000 times. The demo works fine without it. So the model learns that a complete API looks like this:
// What AI typically generates
app.post('/auth/login', async (req, res) => {
const { email, password } = req.body;
const user = await db.users.findOne({ email });
if (!user || !await bcrypt.compare(password, user.passwordHash)) {
return res.status(401).json({ error: 'Invalid credentials' });
}
const token = jwt.sign({ userId: user.id }, process.env.JWT_SECRET);
return res.json({ token });
});
That code will authenticate users just fine. It will also let an attacker try a million passwords against the same account with no resistance at all.
A minimal fix using express-rate-limit:
// Fixed version
import rateLimit from 'express-rate-limit';
const loginLimiter = rateLimit({
windowMs: 15 * 60 * 1000, // 15 minutes
max: 10, // 10 attempts per window per IP
standardHeaders: true,
legacyHeaders: false,
message: { error: 'Too many login attempts. Try again later.' },
});
app.post('/auth/login', loginLimiter, async (req, res) => {
const { email, password } = req.body;
const user = await db.users.findOne({ email });
if (!user || !await bcrypt.compare(password, user.passwordHash)) {
return res.status(401).json({ error: 'Invalid credentials' });
}
const token = jwt.sign({ userId: user.id }, process.env.JWT_SECRET);
return res.json({ token });
});
Four lines of setup and one middleware argument. That is not a lot to ask. The AI did not include it.
It is not just login endpoints
Login is the obvious target but it is not the only one. Password reset endpoints are equally bad, because they can be used to enumerate valid email addresses at scale. Public search routes get scraped. Any endpoint that does real work per request is a billing liability if someone decides to abuse it.
The problem compounds when you consider that many AI-generated projects ship to production faster than a hand-written one would. The developer trusts the scaffolding. Why would they double-check whether the AI added rate limiting? It is not the kind of thing that shows up in a code review when nobody knew to look for it.
What a prompt-first workflow misses
When you are working with an AI coding tool, you are usually thinking about features. You describe what the endpoint should do, the AI writes it, you test that it does the right thing, you move on. The security posture of the deployment is not part of that conversation.
I am not saying AI tools are useless for this. If you explicitly ask for rate limiting, you will get it. That is the problem: you have to know to ask. And if you already knew to ask, you probably could have written it yourself.
There is another wrinkle. Even when you do prompt for rate limiting, the result is often a commented-out block or a placeholder saying "add rate limiting here for production." The model knows the concept exists. It just does not treat it as a requirement.
The mental model that "the AI handles the boilerplate" breaks down for anything that only matters when something goes wrong. Rate limiting and circuit breakers are both invisible until you need them. They do not come up in demos, so they do not get into training data at the density that would make a model include them reliably.
This is also why it keeps happening even as the models get better at writing code. Better code generation does not fix a gap in what the training data treated as necessary.
What to do about it
Do not treat AI-generated API scaffolding as production-ready. Treat it as a starting point that needs an ops review before it ships. Specifically for rate limiting:
- Add per-IP limits on any unauthenticated endpoint that does database work
- Add per-user limits on authenticated endpoints that carry meaningful cost per call
- Add aggressive limits on anything that sends an email or SMS
- Set
standardHeaders: trueso clients can read your rate limit headers and back off instead of hammering
If you are using a framework with middleware support, the implementation is genuinely not difficult. The friction is noticing that it is missing in the first place.
Warning: an IP-only rate limit is bypassable by a distributed attacker. It is still worth doing. Raising the cost of an attack matters even when the ceiling is not perfect.
Seaworthy flags missing rate limiting on authentication and password-reset endpoints as part of its security checks.
Cet article a été généré par IA et résume des sources publiquement disponibles.
Seaworthy analyse votre dépôt pour détecter les problèmes abordés dans des articles comme celui-ci.
Failles de sécurité, secrets exposés et mauvaises configurations détectés avant le déploiement. Gratuit, aucun compte requis.