Your AI Assistant Forgot Pagination. Now Your API Is a DoS Magnet.
By Seaworthy · November 1, 2026
Photo by Ecliptic Graphic on Unsplash
I keep seeing the same generated code in pull requests: a list endpoint that does Model.findMany() or SELECT * FROM items and returns the whole array. It arrives with a cheerful comment like // get all items. The developer accepted it because the tests passed and the local database had nine rows. Six weeks later, production has 400,000 rows, and one unauthenticated GET request can pin a worker for thirty seconds.
This is not a theoretical scaling concern. It is a resilience bug that AI coding assistants make worse by defaulting to the simplest happy path. When you ask for a list endpoint, you get the shortest correct-looking implementation. The model has no idea your orders table grows by 20,000 rows a day, and it will not volunteer pagination unless you push back.
How the failure actually looks
The problem is not just slow responses. An unbounded endpoint multiplies the cost of every request: JSON serialization, memory allocation, database scan time, and network transfer all scale with the full result set. Throw a few concurrent requests at it and you get memory pressure and connection pool exhaustion. A script that fires fifty parallel requests can take down a service that looked healthy a minute earlier. That is a denial-of-service with no exploit code required, just a for loop.
Here is the pattern I keep finding, simplified:
@app.get("/api/orders")
def list_orders():
orders = db.query(Order).all() # no limit, no offset
return [serialize(o) for o in orders]
The fix is not exotic. Add a required limit and an offset or cursor, cap the maximum page size, and return metadata so clients know how to fetch more.
from fastapi import Query
MAX_PAGE = 100
@app.get("/api/orders")
def list_orders(
limit: int = Query(20, ge=1, le=MAX_PAGE),
offset: int = Query(0, ge=0),
):
rows = (
db.query(Order)
.order_by(Order.id)
.offset(offset)
.limit(limit)
.all()
)
return {
"items": [serialize(o) for o in rows],
"limit": limit,
"offset": offset,
"next_offset": offset + len(rows) if len(rows) == limit else None,
}
Two details matter here. First, the server enforces the cap; do not trust a client-supplied limit of 1000000. Second, return a next_offset (or a cursor) so clients can page without guessing. Offset pagination has its own issues on large tables, but it is a vast improvement over returning everything.
Why AI assistants keep making this mistake
Language models optimize for plausible, short, working code. Pagination adds parameters, edge cases, and response shape decisions. It is the kind of operational detail that is easy to leave out of a prompt and easy for a model to skip. If your prompt says "add an endpoint to list orders," you will often get the unbounded version. If it says "add a paginated endpoint capped at 100 items with cursor or offset support," you get something closer to safe.
So the fix has two parts. The first is technical: audit every list endpoint for a hard cap and a paging mechanism. Include this in code review checklists, and add a test that asserts a huge limit gets rejected or clamped. The second is procedural: when you use an AI assistant, state your pagination contract up front, and treat any generated list endpoint without a cap as a defect, not a default.
I find it striking how often teams add rate limiting at the edge but leave an endpoint that returns a gigabyte per request. Rate limits do not save you from a single expensive call. The response size and query cost have to be bounded at the endpoint level.
A quick audit you can run today
Grep your handlers for findMany, findAll, SELECT *, or .all() without a nearby limit. Check whether the limit is validated server-side. Confirm that an empty result and a full page both behave predictably. Then ask your assistant to generate the adversarial test: request one million rows and assert the server clamps it. If the generated test passes against the original code, your model has not understood the problem, and neither will your future self at 2 a.m.
Unbounded list endpoints are boring, common, and expensive. They are exactly the kind of thing an AI assistant will hand you with confidence. Treat pagination as a non-negotiable part of the contract, not an optional optimization.
Seaworthy provides developer-security guidance and tooling for teams building and reviewing AI-assisted code.
This article was generated by AI and summarises publicly available sources.
Seaworthy scans your repo for the issues covered in articles like this one.
Security gaps, exposed secrets, and misconfigurations — caught before you deploy. Free to run, no account needed.