Skip to content

fix(sei-cosmos): harden paginated RPC queries against DoS via limit, offset, and count_total caps (PLT-361)#3494

Open
amir-deris wants to merge 18 commits into
mainfrom
amir/plt-361-lower-sei-cosmos-pagination-query-limit
Open

fix(sei-cosmos): harden paginated RPC queries against DoS via limit, offset, and count_total caps (PLT-361)#3494
amir-deris wants to merge 18 commits into
mainfrom
amir/plt-361-lower-sei-cosmos-pagination-query-limit

Conversation

@amir-deris
Copy link
Copy Markdown
Contributor

@amir-deris amir-deris commented May 21, 2026

Problem

Three separate vectors allow a single RPC call to trigger unbounded KV store iteration:

  1. Limit too largeMaxLimit was math.MaxUint64; callers could request billions of items in one call.
  2. count_total=true unbounded scan — after serving the requested page, the paginator continued iterating the entire remaining store just to populate pagination.total. Implicit limit=0 also silently enabled this behaviour.
  3. Offset too large — no cap on pagination.offset; a caller with offset=1_000_000_000 forces the iterator to skip a billion entries before serving a single result.
  4. GetBlockWithTxs allocation — user-supplied limit was passed directly into make([]*txtypes.Tx, 0, limit) before any validation.

Changes

sei-cosmos/types/query/pagination.go

  • Lowers MaxLimit to 1_000
  • Adds MaxOffset = 10_000 and VerifyPaginationOffset(); enforced in ParsePagination and paginate()
  • Adds MaxScanLimit = 10_000 — fires when count_total=true and the iterator travels more than MaxScanLimit entries past the end of the requested page (count > end + MaxScanLimit), preventing full-store counts while still allowing count_total on reasonably-sized stores
  • Removes the implicit countTotal = true side-effect when limit == 0; callers must opt in explicitly

sei-cosmos/types/query/filtered_pagination.go

  • Same MaxOffset and MaxScanLimit guards applied to FilteredPaginate and GenericFilteredPaginate
  • Fixes a bug in the original scan cap where totalIter (raw store iterations) was compared against end (a filtered-hit count), causing the limit to fire mid-page for selective filters — a query with a 1% pass rate and limit=100 could be rejected before accumulating its first result
  • Replaces the single mixed-space guard with a two-phase approach:
    • Phase 1 (numHits < end): caps raw iterations at end + MaxScanLimit — prevents full-store walks when the filter produces too few hits to fill the page (no-hits DoS path); cannot fire once the page starts completing
    • Phase 2 (numHits >= end): tracks iterations after page completion via pageCompleteIter and caps at MaxScanLimit — limits post-page count_total scanning without any risk of mid-page interference

sei-cosmos/x/auth/tx/service.go

  • Calls pagination.VerifyPaginationLimit(limit) before make([]*txtypes.Tx, 0, limit) in GetBlockWithTxs

Behaviour summary

Request Before After
limit > 1,000 accepted, full scan InvalidArgument
limit = 0 default 100 items + implicit count_total=true default 100 items, no implicit count
count_total=true, store has > end + 10,000 entries full store scan InvalidArgument
count_total=true, selective filter, numHits < end rejected mid-page assembly completes page; errors only if filter is too sparse to fill page within end + MaxScanLimit raw iterations
offset > 10,000 accepted, skips N entries InvalidArgument
GetBlockWithTxs with limit=1_000_000_000 allocates ~1 GB slice InvalidArgument

Regression risk

Breaking change for clients sending limit > 1,000, offset > 10,000, or relying on limit=0 to return a total count. Clients should use limit ≤ 1,000, follow next_key for subsequent pages, and set count_total=true explicitly only when the store is known to be small.

ExportGenesis and the TotalSupply invariant were migrated to collectAllTotalSupply, which internally paginates at MaxLimit per page to collect all denominations without being blocked by the new limit.

🤖 Generated with Claude Code

@amir-deris amir-deris self-assigned this May 21, 2026
@amir-deris amir-deris changed the title Updated max limit for sei_cosmos query pagination fix: lower sei-cosmos pagination query MaxLimit to 10,000 May 21, 2026
@cursor
Copy link
Copy Markdown

cursor Bot commented May 21, 2026

PR Summary

Medium Risk
Breaking change for API clients using limit > 1,000, offset > 10,000, or implicit totals on limit=0; mitigated by bank genesis/invariant using key-based paging.

Overview
This PR hardens Cosmos-style paginated queries against DoS from oversized or unbounded RPC pagination.

sei-cosmos/types/query: MaxLimit drops from math.MaxUint64 to 1,000; new caps MaxOffset (10,000) and MaxScanLimit (10,000) with VerifyPaginationLimit / VerifyPaginationOffset. Paginate, FilteredPaginate, and GenericFilteredPaginate reject bad limit/offset and, when count_total=true, stop scanning after MaxScanLimit entries past the page end (filtered paths use raw iteration counters so sparse filters cannot force full-store walks). limit == 0 no longer implicitly sets count_total—callers must opt in.

x/auth/tx: GetBlockWithTxs validates pagination limit before make([]*Tx, 0, limit) to block huge slice preallocation.

x/bank: ExportGenesis and the TotalSupply invariant use collectAllTotalSupply, which pages at MaxLimit via next_key instead of a single huge page.

Tests and a few REST/gRPC tests were updated for the new totals behavior and explicit count_total where totals are asserted.

Reviewed by Cursor Bugbot for commit 857c301. Bugbot is set up for automated code reviews on this repo. Configure here.

@github-actions
Copy link
Copy Markdown

github-actions Bot commented May 21, 2026

The latest Buf updates on your PR. Results from workflow Buf / buf (pull_request).

BuildFormatLintBreakingUpdated (UTC)
✅ passed✅ passed✅ passed✅ passedMay 27, 2026, 6:45 PM

Comment thread sei-cosmos/types/query/pagination.go Outdated
@codecov
Copy link
Copy Markdown

codecov Bot commented May 21, 2026

Codecov Report

❌ Patch coverage is 73.75000% with 21 lines in your changes missing coverage. Please review.
✅ Project coverage is 59.02%. Comparing base (3936ac9) to head (857c301).

Files with missing lines Patch % Lines
sei-cosmos/types/query/filtered_pagination.go 70.58% 6 Missing and 4 partials ⚠️
sei-cosmos/x/bank/keeper/keeper.go 61.53% 4 Missing and 1 partial ⚠️
sei-cosmos/types/query/pagination.go 92.85% 1 Missing and 1 partial ⚠️
sei-cosmos/x/auth/tx/service.go 0.00% 2 Missing ⚠️
sei-cosmos/x/bank/keeper/genesis.go 50.00% 1 Missing ⚠️
sei-cosmos/x/bank/keeper/invariants.go 0.00% 1 Missing ⚠️
Additional details and impacted files

Impacted file tree graph

@@            Coverage Diff             @@
##             main    #3494      +/-   ##
==========================================
- Coverage   59.04%   59.02%   -0.02%     
==========================================
  Files        2199     2189      -10     
  Lines      182096   181424     -672     
==========================================
- Hits       107513   107087     -426     
+ Misses      64933    64710     -223     
+ Partials     9650     9627      -23     
Flag Coverage Δ
sei-chain-pr 73.74% <73.75%> (?)
sei-db 70.41% <ø> (ø)
sei-db-state-db ?

Flags with carried forward coverage won't be shown. Click here to find out more.

Files with missing lines Coverage Δ
sei-cosmos/x/bank/keeper/genesis.go 76.59% <50.00%> (ø)
sei-cosmos/x/bank/keeper/invariants.go 48.64% <0.00%> (-2.71%) ⬇️
sei-cosmos/types/query/pagination.go 85.84% <92.85%> (+2.30%) ⬆️
sei-cosmos/x/auth/tx/service.go 7.46% <0.00%> (-0.12%) ⬇️
sei-cosmos/x/bank/keeper/keeper.go 79.82% <61.53%> (-0.12%) ⬇️
sei-cosmos/types/query/filtered_pagination.go 66.66% <70.58%> (-0.53%) ⬇️

... and 103 files with indirect coverage changes

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

Comment thread sei-cosmos/types/query/pagination.go
@amir-deris amir-deris changed the title fix: lower sei-cosmos pagination query MaxLimit to 10,000 fix(sei-cosmos): enforce 10,000-item cap on all paginated RPC query entry points May 22, 2026
@amir-deris amir-deris changed the title fix(sei-cosmos): enforce 10,000-item cap on all paginated RPC query entry points fix(sei-cosmos): cap paginated RPC queries at 10,000 items per page (PLT-361) May 22, 2026
Comment thread sei-cosmos/x/bank/keeper/keeper.go
@amir-deris amir-deris requested review from bdchatham and masih May 22, 2026 22:48
Comment thread sei-cosmos/types/query/pagination.go
@amir-deris amir-deris changed the title fix(sei-cosmos): cap paginated RPC queries at 10,000 items per page (PLT-361) fix(sei-cosmos): cap paginated RPC queries at 1,000 items per page (PLT-361) May 26, 2026
@amir-deris amir-deris changed the title fix(sei-cosmos): cap paginated RPC queries at 1,000 items per page (PLT-361) fix(sei-cosmos): harden paginated RPC queries against DoS via limit, offset, and count_total caps (PLT-361) May 26, 2026
Copy link
Copy Markdown

@cursor cursor Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cursor Bugbot has reviewed your changes using default effort and found 1 potential issue.

Fix All in Cursor

❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.

Reviewed by Cursor Bugbot for commit d728fdf. Configure here.

Comment thread sei-cosmos/types/query/filtered_pagination.go
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant