# A pagination cursor is not a forever pass

List endpoints often authorize once on page one, then trust the cursor for every next page. That is how a revoked membership keeps paging through results you meant to cut off.

## The bug pattern

A common flow:

1. `GET /items?limit=50` runs a real authorize + filter for the caller.
2. The response returns `next_cursor` encoding offset, last id, or a signed blob of the original filter.
3. `GET /items?cursor=...` skips the policy check and just resumes the scan.

Between page one and page three, the caller can lose access: role revoked, tenant membership ended, object ownership transferred, or a parent resource soft-deleted. The cursor still works because nothing re-evaluated actor + action + resource set.

## Why "the cursor is signed" is not enough

Signing the cursor stops *tampering*. It does not freeze a permission decision in amber.

- A signed `subject_id` + `filter` from ten minutes ago is still yesterday's grant.
- Opaque cursors that only store `last_seen_id` silently inherit whatever the DB returns next — including rows the caller should no longer see.
- Export / infinite-scroll clients hold cursors for a long time; revocation windows matter more there than on a single HTML page.

Integrity of the cursor ≠ freshness of authorization.

## What to enforce

On every page request, including cursor continuations:

- **Re-derive the allowed set.** Run the same authz filter (or PDP query) you used for page one, then apply the cursor *inside* that set.
- **Bind identity tightly.** If the cursor carries a subject or tenant, it must match the current session; mismatch → 401/403, not a helpful resume.
- **Fail closed on stale grants.** Prefer short cursor TTL plus re-check over "resume forever for UX."
- **Watch secondary paths.** GraphQL connections, search "load more," and async exporters that replay cursors need the same rule — not only the primary REST list handler.

## Tiny checklist

1. Does `?cursor=` hit the same authorize/filter path as the first page?
2. Can a revoked user still walk pages with an old cursor?
3. Does changing object ownership mid-scroll remove rows from later pages?
4. Are export jobs using the same rule, or a privileged "scan everything" shortcut?

Pagination is a read strategy. Authorization has to stay live on every page, not only the first.

