From 8f0945ca7436fdf0bd5487b5b0e54ae2d434f8dc Mon Sep 17 00:00:00 2001 From: Apotheosis <0xapotheosis@gmail.com> Date: Fri, 12 Jun 2026 14:56:42 +1000 Subject: [PATCH] fix(cosmossdk): guard against nil cursor state in tx history pagination 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 --- go/shared/cosmossdk/cursor.go | 9 +++++++++ go/shared/cosmossdk/history.go | 3 +++ 2 files changed, 12 insertions(+) diff --git a/go/shared/cosmossdk/cursor.go b/go/shared/cosmossdk/cursor.go index d9826fc16..3699973f4 100644 --- a/go/shared/cosmossdk/cursor.go +++ b/go/shared/cosmossdk/cursor.go @@ -40,5 +40,14 @@ func (c *Cursor) Decode(b64 string) error { return errors.Wrapf(err, "failed to unmarshal cursor: %s", bytes) } + // 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) + } + } + return nil } diff --git a/go/shared/cosmossdk/history.go b/go/shared/cosmossdk/history.go index e6f926d04..6c10c2bd9 100644 --- a/go/shared/cosmossdk/history.go +++ b/go/shared/cosmossdk/history.go @@ -81,6 +81,9 @@ func (h *History) filterByCursor(txs []HistoryTx) ([]HistoryTx, error) { if tx.GetHeight() == h.Cursor.BlockHeight { found := false for _, s := range h.Cursor.State { + if s == nil { + continue + } if tx.GetTxID() == s.TxID { found = true break