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 30 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 30 commits into
mainfrom
amir/plt-361-lower-sei-cosmos-pagination-query-limit

Conversation

@amir-deris

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

Copy link
Copy Markdown
Contributor

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

cursor Bot commented May 21, 2026

Copy link
Copy Markdown

PR Summary

Medium Risk
API behavior changes (lower max limit/offset, no implicit count on limit=0) may break clients; genesis/invariant supply collection now depends on multi-page key pagination.

Overview
Hardens paginated RPC / KV queries against DoS by bounding limit, offset, and how far iterators may scan when assembling pages or count_total.

sei-cosmos/types/query: MaxLimit drops from math.MaxUint64 to 1,000; adds MaxOffset (10,000), MaxScanLimit (10,000), and VerifyPaginationLimit / VerifyPaginationOffset. Paginate, FilteredPaginate, and GenericFilteredPaginate reject oversized requests and cap store walks; limit == 0 no longer forces count_total=true. Filtered pagination uses a two-phase scan cap (raw iterations while filling the page vs. post-page for next_key / totals) so sparse filters are not rejected mid-page incorrectly.

Call sites: GetBlockWithTxs validates limit before make(..., limit). Bank ExportGenesis and TotalSupply invariant use new collectAllTotalSupply (key-based paging at MaxLimit) instead of a single huge page. Tests and REST/gRPC expectations updated where totals now require explicit count_total=true.

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

@github-actions

github-actions Bot commented May 21, 2026

Copy link
Copy Markdown

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

BuildFormatLintBreakingUpdated (UTC)
✅ passed✅ passed✅ passed✅ passedJun 9, 2026, 6:06 PM

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

codecov Bot commented May 21, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 63.82979% with 34 lines in your changes missing coverage. Please review.
✅ Project coverage is 58.27%. Comparing base (7271641) to head (5dd50cd).

Files with missing lines Patch % Lines
sei-cosmos/types/query/filtered_pagination.go 53.84% 18 Missing and 6 partials ⚠️
sei-cosmos/x/bank/keeper/keeper.go 61.53% 4 Missing and 1 partial ⚠️
sei-cosmos/x/auth/tx/service.go 0.00% 3 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.22%   58.27%   -0.95%     
==========================================
  Files        2225     2131      -94     
  Lines      183636   173751    -9885     
==========================================
- Hits       108759   101254    -7505     
+ Misses      65101    63525    -1576     
+ Partials     9776     8972     -804     
Flag Coverage Δ
sei-chain-pr 73.61% <63.82%> (?)
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/types/query/pagination.go 86.59% <100.00%> (+3.05%) ⬆️
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/x/auth/tx/service.go 7.51% <0.00%> (-0.06%) ⬇️
sei-cosmos/x/bank/keeper/keeper.go 79.82% <61.53%> (-0.12%) ⬇️
sei-cosmos/types/query/filtered_pagination.go 62.35% <53.84%> (-4.84%) ⬇️

... and 200 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
Comment thread sei-cosmos/types/query/filtered_pagination.go
Comment thread sei-cosmos/types/query/filtered_pagination.go
Comment thread sei-cosmos/types/query/pagination.go Outdated
Comment thread sei-cosmos/types/query/pagination_test.go Outdated
Comment thread sei-cosmos/types/query/filtered_pagination.go Outdated
Comment thread sei-cosmos/types/query/pagination.go
Comment thread sei-cosmos/x/auth/tx/service.go

@cursor cursor Bot left a comment

Copy link
Copy Markdown

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 6b0b9fc. 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