# Secure Backward Cursor Pagination for Chronological Message Pages

Paginate older messages without gaps while binding an opaque signed continuation cursor to the authorized query and enforcing authorization at fetch time.

Exact reference: {"kind":"skill_version","skill_id":"skl_RNMQgHZcd3l7xevaMYUX8g","version_id":"skv_maLFMvhhObK3gVzhbhZvHQ"}

Applicability: [{"constraint":"Backward keyset pagination with chronological page output and user-controlled continuation cursors","technology":"Message and event APIs","version_scheme":"unknown"}]

# Secure backward cursor pagination

## Trigger

Use when an API queries older messages in descending keyset order, presents each page chronologically, and accepts a user-controlled cursor that may survive a conversation or filter change.

## Practical steps

1. Choose an immutable total order; add a unique tie-breaker when sequence alone is not unique. Authenticate the caller, authorize the conversation selected by the current request, and canonicalize every effective filter and authorization scope. The cursor must never select that scope.
2. On the first page, fix a high-water snapshot. Query within it in descending order for page size plus one rows, applying any exclusive older-than position. If an extra row exists, discard the last descending row before reversing the retained rows.
3. Return the retained rows chronologically. When overflow exists, set the next exclusive position to the oldest retained row, not the discarded lookahead row. Preserve the original snapshot on later pages.
4. Issue a MAC-protected random handle that resolves to server-side cursor state bound to purpose, version, caller or authorization scope, conversation, canonical filters, direction, sort definition, snapshot, position, page size if fixed, and expiry. A readable signed payload provides integrity but not secrecy; use an opaque handle or authenticated encryption to avoid exposing internal identifiers.
5. On continuation, verify authenticity and expiry, independently authorize the currently requested conversation, and compare its effective bindings with the saved state before querying. Enforce authorization in the row fetch or an equivalent atomic boundary so revocation between a preliminary check and the fetch cannot expose rows.
6. Reject tampering, expiry, or any binding mismatch with the same generic invalid-cursor response. Do not echo decoded state, identifiers, or mismatch details. A client may clear a stale cursor when scope changes, but server validation remains required.

## Limits

A high-water mark excludes later appends but does not freeze edits, deletions, backdated inserts, or changing filter membership. Strict repeatability needs immutable or versioned records, a materialized result set, or durable database-snapshot semantics. Token retention, key rotation, replay policy, and expiry are deployment choices. This guidance is reasoned analysis, including an independent reasoning check, not executed tests.

## Failure prevented

Avoids skipped overflow rows, duplicate or missing pages, cross-conversation or cross-filter reuse, direction or snapshot confusion, tampered positions, internal-identifier disclosure, and authorization-revocation races.


## Supporting basis and limitations

The supporting conversation reasons through an eight-message, page-size-three pagination example and a signed opaque cursor threat model. A separate independent reasoning check found no concrete error in the algorithm and identified the authorization revocation race as an implementation caveat. No executable tests were run.

## Change and rationale

Adds the query-time authorization boundary and condenses the already matching pagination and cursor-binding guidance.

An exact current skill already covers backward chronological message pagination and stale user-controlled cursors. Updating it incorporates the independently identified revocation-race caveat without creating duplicate guidance.
