Skip to content

fix(cosmossdk): guard nil cursor state in tx history pagination#1277

Closed
0xApotheosis wants to merge 1 commit into
developfrom
fix/cosmos-cursor-nil-deref-dos
Closed

fix(cosmossdk): guard nil cursor state in tx history pagination#1277
0xApotheosis wants to merge 1 commit into
developfrom
fix/cosmos-cursor-nil-deref-dos

Conversation

@0xApotheosis

@0xApotheosis 0xApotheosis commented Jun 12, 2026

Copy link
Copy Markdown
Member

Description

A caller-supplied cursor is unmarshalled into the server-initialized pagination state. json.Unmarshal into the existing State map keeps unexpected keys and stores a JSON null value as a nil *CursorState. filterByCursor then iterates Cursor.State and dereferences each entry, so a cursor carrying a null state value hits a nil pointer. That dereference runs in an errgroup worker goroutine, so the panic isn't recovered by the request handler and takes down the process instead of just failing the one request.

  • Cursor.Decode drops nil *CursorState entries left by a malformed cursor.
  • filterByCursor skips nil entries defensively.

Verification

Didn't run go build/tests locally per the repo CLAUDE.md (CI handles build validation). It's a nil check plus dropping nil map values; no behaviour change on well-formed cursors.

A caller-controlled `cursor` is unmarshalled into the server-initialized
pagination state. json.Unmarshal into the existing State map keeps unexpected
keys and turns a JSON null value into a nil *CursorState, which filterByCursor
then dereferences inside an errgroup worker goroutine. That panic is outside the
net/http handler goroutine, so it isn't recovered and crashes the process.

- Cursor.Decode drops nil state entries left by a malformed cursor.
- filterByCursor skips nil state entries defensively.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@0xApotheosis 0xApotheosis requested a review from a team as a code owner June 12, 2026 04:57
@coderabbitai

coderabbitai Bot commented Jun 12, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

Two complementary nil-safety fixes address cursor state handling: Decode removes nil entries from the deserialized State map, and filterByCursor skips nil entries during transaction ID comparison to prevent nil pointer dereferences.

Changes

Cursor state nil safety

Layer / File(s) Summary
Cursor state nil value cleanup and filtering guard
go/shared/cosmossdk/cursor.go, go/shared/cosmossdk/history.go
Decode post-processes the unmarshaled State map to remove entries with nil values, preventing malformed cursors from containing nil pointers. filterByCursor adds a guard to skip nil entries when iterating over cursor states during transaction ID filtering.

Estimated code review effort

🎯 2 (Simple) | ⏱️ ~8 minutes

Poem

A cursor once held a nil ghost,
Haunting each transaction post,
But now we clean it up with care,
And skip the empty vacant stare,
No derefs lost in cryptic cost! 🐰

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly and concisely describes the main change: guarding against nil cursor state entries in transaction history pagination. It accurately reflects the core fix across both modified files.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/cosmos-cursor-nil-deref-dos

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@coderabbitai coderabbitai 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.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@go/shared/cosmossdk/cursor.go`:
- Around line 43-50: The loop in cursor.go that deletes nil entries from c.State
causes missing keys and can lead to nil-pointer dereferences later (e.g.,
history.Cursor.State[source].Page in tx.go); instead of delete(c.State, source)
replace nil values with a non-nil default (e.g., c.State[source] =
&CursorState{} or an equivalent zero-value CursorState) so the key remains
present but safe to dereference; update the loop that iterates c.State to assign
a default CursorState when state == nil.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 10c2a88a-c706-4acf-a937-072caf8be4b5

📥 Commits

Reviewing files that changed from the base of the PR and between 1b3e059 and 8f0945c.

📒 Files selected for processing (2)
  • go/shared/cosmossdk/cursor.go
  • go/shared/cosmossdk/history.go

Comment on lines +43 to +50
// json.Unmarshal into the existing State map keeps caller-supplied keys and stores a JSON
// null pointer value as a nil *CursorState. Drop those nil entries so a malformed cursor
// (e.g. {"state":{"x":null}}) can't leave a nil pointer to be dereferenced downstream.
for source, state := range c.State {
if state == nil {
delete(c.State, source)
}
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Deleting nil entries can still leave a nil-deref path for expected sources.

At Line 48, deleting c.State[source] removes the initialized entry entirely. In go/shared/cosmossdk/tx.go (context snippet, source-page sync loop), history.Cursor.State[source].Page is dereferenced without an existence check after Decode; a cursor like {"state":{"<expected_source>":null}} can still panic there.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@go/shared/cosmossdk/cursor.go` around lines 43 - 50, The loop in cursor.go
that deletes nil entries from c.State causes missing keys and can lead to
nil-pointer dereferences later (e.g., history.Cursor.State[source].Page in
tx.go); instead of delete(c.State, source) replace nil values with a non-nil
default (e.g., c.State[source] = &CursorState{} or an equivalent zero-value
CursorState) so the key remains present but safe to dereference; update the loop
that iterates c.State to assign a default CursorState when state == nil.

@0xApotheosis 0xApotheosis deleted the fix/cosmos-cursor-nil-deref-dos branch June 12, 2026 05:15
@0xApotheosis

Copy link
Copy Markdown
Member Author

Superseded by #1279 (branch renamed).

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant