# Secure backward pagination with context-bound cursors

Keep newest-first storage reads gap-free while returning chronological pages and rejecting opaque cursor reuse across callers, resources, filters, directions, or snapshots.

Exact reference: {"kind":"skill_version","skill_id":"skl_FL2WVf2TdPZzv6zYXgRLlQ","version_id":"skv_VJWIIwKnD0620LA2OGde4w"}

Applicability: []

# Secure backward pagination with context-bound cursors

## Trigger

Use this when storage is read newest-first with page size plus one, responses are returned chronologically, and a client-controlled cursor may survive a change of caller, resource, filters, direction, or snapshot.

## Practical steps

1. Authenticate the caller through the normal API path before processing a cursor. On every page, independently authorize the resource named by the current request; never let cursor state choose request scope.
2. Use one deterministic immutable total order. Add a unique tie breaker when the display key is not unique, and use the complete tuple for ordering and continuation.
3. On the first request, resolve defaults and authorization-dependent restrictions, canonicalize the effective resource, filters and direction, and capture a stable snapshot. Fetch page size plus one rows in descending order.
4. Detect overflow before reversing. Remove the final descending row, which is the oldest lookahead. Build the exclusive next position from the oldest retained row, reverse only the retained rows, and omit the cursor when no lookahead exists.
5. Bind the cursor to its version, endpoint purpose, authenticated security boundary, canonical resource, canonical filters, direction, sort policy, original snapshot, complete keyset position and expiry.
6. Keep claims opaque. Prefer a signed random server-side handle of at least 128 bits, or authenticated encryption for stateless claims. A readable signed payload prevents alteration but does not hide identifiers or positions.
7. On continuation, bound token size and parsing. For a signed handle, verify the signature in constant time before lookup. For authenticated encryption, open and authenticate the envelope before parsing claims; there is no separate signature step unless an outer HMAC is also used. If present, verify that outer HMAC first.
8. After successful token authentication, check version and expiry, reauthorize the requested resource, recompute every current binding, compare bindings in constant time, and query using both the original snapshot and the exclusive position. Preserve the snapshot and bindings in the next cursor; advance only the position.
9. After caller authentication, use one generic invalid-cursor response for malformed, expired, mismatched, unavailable-snapshot or unauthorized continuation state. Do not echo or log tokens, claims, identifiers or differing values. Keep telemetry to fixed low-cardinality reason codes.

## 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. Use an MVCC as-of view, versioned rows or a materialized result when edits, deletes, filter membership changes or late-visible older rows must remain frozen.

## Limits

The design assumes a bounded page size and a deterministic immutable composite order. Canonicalization must exactly match query semantics, including defaults, absence, types, duplicate handling, authorization-dependent restrictions, and ordered versus set-like values. A cursor snapshot must remain serviceable for its advertised lifetime. Clearing stale cursors in the UI improves usability but never replaces server validation. Per-page authorization is mandatory even when the cursor is bound to a caller.

## Failure prevented

This prevents skipped boundary rows, duplicate traversal, reuse of a valid cursor against another caller or query, disclosure of internal identifiers in readable signed tokens, mixing pages from different snapshots, authorization confusion, and mismatch errors that reveal protected state.

## Supporting basis and limitations

Supported by reasoned analysis in the cited task conversation and an independent reasoning review. No executable tests were run.

## Change and rationale

Clarifies caller authentication before cursor handling, distinguishes AEAD opening from outer-HMAC verification, and focuses the guidance on pagination, context binding, snapshot stability, and non-disclosing rejection.

A client-controlled position is unsafe when UI state can outlive a resource or filter change. The current skill matches exactly, but its validation sequence needs precise authentication and cryptographic wording.
