List endpoints without pagination caps are resource exhaustion vectors and data exfiltration shortcuts. OWASP API Security Top 10 2023 API4 (Unrestricted Resource Consumption) and CWE-400 (Uncontrolled Resource Consumption) both target this pattern. A single request for ?limit=1000000 can trigger a full table scan, exhaust database memory, and return megabytes of records in a single response. Beyond DoS, unbounded pagination is a data exfiltration technique: a single API call retrieves every user record, every order, or every payment transaction. Even authenticated users should be limited to reasonable page sizes — trust boundaries apply to consumption, not just access.
Low because pagination bypass requires an authenticated request, but the impact — full table dumps and database memory exhaustion — scales with data volume.
Clamp the limit parameter server-side — never trust the client-supplied value directly. Apply the cap in the data access layer, not just the route handler, so it can't be bypassed by callers that go around the API.
// src/lib/db/posts.ts
const MAX_PAGE_SIZE = 100
const DEFAULT_PAGE_SIZE = 20
export async function listPosts({ limit = DEFAULT_PAGE_SIZE, cursor }: PaginationArgs) {
const take = Math.min(Math.max(1, limit), MAX_PAGE_SIZE) // clamp: [1, 100]
return db.post.findMany({
take,
skip: cursor ? 1 : 0,
cursor: cursor ? { id: cursor } : undefined,
orderBy: { createdAt: 'desc' }
})
}
Return the applied limit and nextCursor in responses so clients know the actual window size.
ID: api-security.abuse-prevention.pagination-limits
Severity: low
What to look for: Enumerate every relevant item. Check endpoints that return lists of data (users, posts, comments). Verify that pagination is enforced with a maximum limit, preventing requests for unlimited results.
Pass criteria: At least 1 of the following conditions is met. List endpoints accept limit and offset/cursor parameters but cap the maximum limit (e.g., max 100 items per page). Requests for more than the max are capped or return a 400 error.
Fail criteria: Endpoints allow requests for unlimited results, or the limit parameter is not enforced.
Skip (N/A) when: The API does not return list data.
Detail on fail: "GET /api/posts accepts unlimited limit parameter — requests can retrieve all posts at once" or "No pagination implemented on list endpoints"
Remediation: Enforce pagination limits:
const MAX_LIMIT = 100
const DEFAULT_LIMIT = 20
export default async (req, res) => {
let limit = parseInt(req.query.limit) || DEFAULT_LIMIT
const offset = parseInt(req.query.offset) || 0
// Cap the limit
if (limit > MAX_LIMIT) limit = MAX_LIMIT
if (limit < 1) limit = 1
const posts = await db.post.findMany({
take: limit,
skip: offset
})
res.json(posts)
}