fix(sei-cosmos): harden paginated RPC queries against DoS via limit, offset, and count_total caps (PLT-361)#3494
Conversation
PR SummaryMedium Risk Overview
Tests and a few REST/gRPC tests were updated for the new totals behavior and explicit Reviewed by Cursor Bugbot for commit 857c301. Bugbot is set up for automated code reviews on this repo. Configure here. |
|
The latest Buf updates on your PR. Results from workflow Buf / buf (pull_request).
|
Codecov Report❌ Patch coverage is Additional details and impacted files@@ 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
Flags with carried forward coverage won't be shown. Click here to find out more.
🚀 New features to boost your workflow:
|
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes using default effort and found 1 potential issue.
❌ 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.

Problem
Three separate vectors allow a single RPC call to trigger unbounded KV store iteration:
MaxLimitwasmath.MaxUint64; callers could request billions of items in one call.count_total=trueunbounded scan — after serving the requested page, the paginator continued iterating the entire remaining store just to populatepagination.total. Implicitlimit=0also silently enabled this behaviour.pagination.offset; a caller withoffset=1_000_000_000forces the iterator to skip a billion entries before serving a single result.GetBlockWithTxsallocation — user-suppliedlimitwas passed directly intomake([]*txtypes.Tx, 0, limit)before any validation.Changes
sei-cosmos/types/query/pagination.goMaxLimitto1_000MaxOffset = 10_000andVerifyPaginationOffset(); enforced inParsePaginationandpaginate()MaxScanLimit = 10_000— fires whencount_total=trueand the iterator travels more thanMaxScanLimitentries past the end of the requested page (count > end + MaxScanLimit), preventing full-store counts while still allowingcount_totalon reasonably-sized storescountTotal = trueside-effect whenlimit == 0; callers must opt in explicitlysei-cosmos/types/query/filtered_pagination.goMaxOffsetandMaxScanLimitguards applied toFilteredPaginateandGenericFilteredPaginatetotalIter(raw store iterations) was compared againstend(a filtered-hit count), causing the limit to fire mid-page for selective filters — a query with a 1% pass rate andlimit=100could be rejected before accumulating its first resultnumHits < end): caps raw iterations atend + 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 completingnumHits >= end): tracks iterations after page completion viapageCompleteIterand caps atMaxScanLimit— limits post-pagecount_totalscanning without any risk of mid-page interferencesei-cosmos/x/auth/tx/service.gopagination.VerifyPaginationLimit(limit)beforemake([]*txtypes.Tx, 0, limit)inGetBlockWithTxsBehaviour summary
limit > 1,000InvalidArgumentlimit = 0count_total=truecount_total=true, store has >end + 10,000entriesInvalidArgumentcount_total=true, selective filter,numHits < endend + MaxScanLimitraw iterationsoffset > 10,000InvalidArgumentGetBlockWithTxswithlimit=1_000_000_000InvalidArgumentRegression risk
Breaking change for clients sending
limit > 1,000,offset > 10,000, or relying onlimit=0to return a total count. Clients should uselimit ≤ 1,000, follownext_keyfor subsequent pages, and setcount_total=trueexplicitly only when the store is known to be small.ExportGenesisand theTotalSupplyinvariant were migrated tocollectAllTotalSupply, which internally paginates atMaxLimitper page to collect all denominations without being blocked by the new limit.🤖 Generated with Claude Code