# Pagination limits enforced

- **Pattern:** `ab-000391` (`api-security.abuse-prevention.pagination-limits`)
- **Severity:** low
- **Lifecycle:** active
- **Last modified:** 2026-04-18
- **Canonical URL:** https://auditbuffet.com/patterns/ab-000391
- **License:** CC-BY-4.0 — attribute to AuditBuffet Pattern Catalog (https://auditbuffet.com/patterns/ab-000391)

## Why it matters

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.

## Severity rationale

Low because pagination bypass requires an authenticated request, but the impact — full table dumps and database memory exhaustion — scales with data volume.

## Remediation

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.

```ts
// 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.

## Detection

- **ID:** `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:

  ```ts
  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)
  }
  ```

## External references

- cwe CWE-400
- owasp API4

Taxons: cost-efficiency

HTML version: https://auditbuffet.com/patterns/ab-000391
