# Secure backward pagination with context-bound cursors
## Trigger
Use this when storage is read newest-first with page size plus one, responses are returned oldest-first, and a client-held cursor may survive changes to the selected resource, filters, or direction.
## Practical steps
1. Query by the complete unique sort key in descending order for page size plus one.
2. Detect overflow before changing order. Remove the final fetched row, which is the oldest lookahead.
3. Build the exclusive next position from the oldest retained row, then reverse only the retained rows for chronological output. Omit the cursor when there is no lookahead.
4. Protect token version, endpoint, authenticated security boundary, canonical resource, canonical filters, direction, sort policy, snapshot, complete keyset position, and expiry.
5. Keep the entire claim set opaque. Use authenticated encryption, or a random server-side handle of at least 128 bits authenticated before lookup. A readable signed payload provides integrity but not confidentiality.
6. On continuation, authenticate the token before trusting fields, recompute every request binding, reauthorize current access, and apply both the snapshot and exclusive position.
7. Return one generic invalid-cursor response for malformed, expired, mismatched, missing, or unauthorized state. Never echo or log tokens, claims, identifiers, or differing values.
8. Bound telemetry with a closed reason vocabulary and at most one warning per request. Permit only fixed dimensions such as endpoint and service version.
## Snapshot requirement
A maximum display key is not automatically a stable snapshot. Backdated inserts, equal-key inserts, or keys allocated before commit can become visible later below the cursor. For append-only data, use a committed insertion watermark whose order matches visibility and constrain every page to the initial watermark. For stronger consistency, use an MVCC as-of view or materialized result. Freezing edits and deletes requires those stronger mechanisms or versioned rows.
## Auxiliary effects
Keep background publication or sharing outside the primary pagination path. Absence of an acknowledgement does not prove that an external effect did not commit before a worker crash. If a worker can change visibility, use an authoritative durable gate or an idempotent durable outbox with status reconciliation and fencing. Let the primary request finish after a worker deadline, but do not infer sharing permission from an ambiguous outcome.
## Limits
The design assumes a deterministic immutable composite order. Canonicalization must exactly match query semantics, including defaults, absence, types, duplicate handling, and ordered versus set-like values. Client-side cursor clearing improves usability but never replaces server validation. A high-water insertion watermark alone does not freeze updates or deletions.
## Failure prevented
This prevents skipped boundary rows, duplicate traversal, reuse of a valid cursor against another resource or filter set, disclosure of sensitive cursor state, phantom rows caused by weak snapshots, high-cardinality diagnostic leakage, and incorrect fail-open behavior after an auxiliary worker crashes.