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 28 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 28 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

High Risk
Changes core query pagination behavior for all modules with breaking client semantics (limits, totals, large count_total scans) while protecting node RPC from resource exhaustion.

Overview
This PR hardens Cosmos-style query pagination so a single RPC cannot walk unbounded KV state.

types/query: MaxLimit drops to 1,000; new MaxOffset (10,000) and MaxScanLimit (10,000) with VerifyPaginationLimit / VerifyPaginationOffset used in Paginate, ParsePagination, and filtered paths. limit == 0 no longer implicitly sets count_total—callers must opt in. Offset pagination errors when scans exceed the cap (including post-page work for count_total). FilteredPaginate uses a two-phase scan limit so sparse filters are not rejected mid-page while still blocking full-store walks.

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

x/bank: genesis export and the total-supply invariant use collectAllTotalSupply (key-based pages at MaxLimit) instead of one giant paginated read.

Tests and a few REST/gRPC cases now pass pagination.count_total=true where totals are asserted.

Breaking for API clients: limit > 1_000, offset > 10_000, relying on limit=0 for totals, or count_total over large stores may now return InvalidArgument; use next_key and explicit, bounded count_total.

Reviewed by Cursor Bugbot for commit a2c5e5b. 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 29, 2026, 8:46 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 63.82979% with 34 lines in your changes missing coverage. Please review.
✅ Project coverage is 58.31%. Comparing base (cd0db11) to head (a2c5e5b).
⚠️ Report is 2 commits behind head on main.

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.15%   58.31%   -0.85%     
==========================================
  Files        2204     2131      -73     
  Lines      182630   174335    -8295     
==========================================
- Hits       108043   101665    -6378     
+ Misses      64899    63655    -1244     
+ Partials     9688     9015     -673     
Flag Coverage Δ
sei-chain-pr 73.61% <63.82%> (?)
sei-db 70.41% <ø> (-0.22%) ⬇️
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 74 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
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 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