Skip to content

fix: oracle response-body bound, RLP length overflow guard, metrics port validation - #1033

Closed
curryxbo wants to merge 6 commits into
mainfrom
fix/token-price-oracle-bound-response-body
Closed

fix: oracle response-body bound, RLP length overflow guard, metrics port validation#1033
curryxbo wants to merge 6 commits into
mainfrom
fix/token-price-oracle-bound-response-body

Conversation

@curryxbo

@curryxbo curryxbo commented Aug 10, 2026

Copy link
Copy Markdown
Contributor

Summary

Small, independent hardening fixes surfaced during code review, grouped into one PR.

1. Bound HTTP price response body (token-price-oracle)

getJSONWithHeaders read price responses with an unbounded io.ReadAll, so a compromised or misbehaving CEX/Hermes endpoint could stream an arbitrarily large body and exhaust memory (the 10s client timeout limits time, not size). Cap the read at 1 MiB via io.LimitReader and reject anything larger. The helper is shared by the Binance, OKX and Pyth paths, so one change covers all three; Chainlink is unaffected (on-chain RPC).

2. Reject RLP tx length that overflows uint32 (common/batch)

In extractInnerTxFullBytes, size is a uint32, so 1+sizeByteLen+size can exceed MaxUint32 and wrap to a tiny buffer length, leaving fullTxBytes shorter than the copies that follow and panicking. The size > remaining guard from #1028 keeps this unreachable on the current decode path (the declared length is bounded by the decompressed stream, itself bounded upstream), so this is defensive: compute the length in uint64 and reject the overflow before allocating, keeping the decoder safe if the size type or upstream bounds ever change. No behavior change on valid input.

3. Validate layer1 metrics port range (node)

MetricsPort (uint64) had no upper-bound check, so a misconfigured value (e.g. >65535) produced an invalid listen address whose metrics ListenAndServe failed silently in the background. Reject a port outside 1..65535 in SetCliContext so the misconfig fails at startup. The default (26660, matching Tendermint's instrumentation port so layer1 validators stay consistent with other node types) is always in range, so valid configs are unaffected.

Test plan

  • token-price-oracle: go build ./..., go vet ./client/, go test ./client/ (added TestGetJSONRejectsOversizedBody, TestGetJSONAcceptsBodyAtLimit)
  • common/batch: CGO_ENABLED=1 go build/vet/test ./batch/ (existing TestExtractInnerTxFullBytes* pass)
  • node/derivation: go build/vet/test ./derivation/ (added TestMetricsPort_AcceptsDefaultInLayer1, TestMetricsPort_RejectsOutOfRange)

getJSONWithHeaders read price responses with an unbounded io.ReadAll,
so a compromised or misbehaving CEX/Hermes endpoint could stream an
arbitrarily large body and exhaust memory. Cap the read at 1 MiB via
io.LimitReader (ticker and Hermes latest-price payloads are a few KB),
rejecting anything larger. Covers the Binance, OKX and Pyth paths that
share this helper.

Co-authored-by: Cursor <cursoragent@cursor.com>
@curryxbo
curryxbo requested a review from a team as a code owner August 10, 2026 14:57
@curryxbo
curryxbo requested review from twcctop and removed request for a team August 10, 2026 14:57

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

Claude Code Review

Claude Code Review is paused for this repository. To reconnect it, an admin of this repository's GitHub organization (or the account owner, for personal repositories) who can also manage your Claude organization's Code Review settings needs to re-link GitHub in Code Review settings. This is a one-time step.

Tip: disable this comment in your organization's Code Review settings.

@coderabbitai

coderabbitai Bot commented Aug 10, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

📝 Walkthrough

Walkthrough

The price client rejects HTTP bodies larger than 1 MiB. Tests cover oversized and boundary responses. Transaction-buffer length arithmetic now uses uint64 operands to prevent overflow.

Changes

Response-body limits

Layer / File(s) Summary
Enforce and test response-body limits
token-price-oracle/client/cex_feed.go, token-price-oracle/client/cex_feed_test.go
The client limits response bodies to 1 MiB. Tests verify rejection above the limit and acceptance at the limit.

Transaction buffer sizing

Layer / File(s) Summary
Prevent buffer-length overflow
common/batch/blob.go
extractInnerTxFullBytes uses uint64 arithmetic for transaction-buffer lengths.

Estimated code review effort: 2 (Simple) | ~10 minutes

Suggested reviewers: twcctop, dylancai9

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
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.
Title check ✅ Passed The title clearly identifies the oracle response-body limit and RLP overflow fix, which are present in the changeset, although metrics port validation is not shown.
✨ 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/token-price-oracle-bound-response-body

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.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

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 `@token-price-oracle/client/cex_feed_test.go`:
- Around line 118-133: Update TestGetJSONAcceptsBodyAtLimit so the valid JSON
payload is padded with trailing whitespace using strings.Repeat until its length
is exactly maxResponseBodyBytes; add the strings import if needed, while
preserving the existing request and body-equality assertions.
🪄 Autofix

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: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: b211c62c-f57d-4427-a23d-9a1bf1c21370

📥 Commits

Reviewing files that changed from the base of the PR and between d79f4fd and 6ee98f2.

📒 Files selected for processing (2)
  • token-price-oracle/client/cex_feed.go
  • token-price-oracle/client/cex_feed_test.go

Comment on lines +118 to +133
func TestGetJSONAcceptsBodyAtLimit(t *testing.T) {
payload := `{"symbol":"BTCUSDT","price":"64385.12"}`
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "application/json")
w.Write([]byte(payload))
}))
defer server.Close()

body, err := getJSON(context.Background(), server.Client(), server.URL)
if err != nil {
t.Fatal(err)
}
if string(body) != payload {
t.Fatalf("body = %q, want %q", string(body), payload)
}
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Make the boundary test send exactly maxResponseBodyBytes bytes.

The current payload is much smaller than the limit. This test does not verify the exact boundary and would pass even if boundary handling were incorrect. Pad the valid JSON with trailing whitespace to reach the configured byte limit.

Proposed fix
-	payload := `{"symbol":"BTCUSDT","price":"64385.12"}`
+	payload := `{"symbol":"BTCUSDT","price":"64385.12"}`
+	payload += strings.Repeat(" ", maxResponseBodyBytes-len(payload))

Add "strings" to the import block if needed.

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
func TestGetJSONAcceptsBodyAtLimit(t *testing.T) {
payload := `{"symbol":"BTCUSDT","price":"64385.12"}`
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "application/json")
w.Write([]byte(payload))
}))
defer server.Close()
body, err := getJSON(context.Background(), server.Client(), server.URL)
if err != nil {
t.Fatal(err)
}
if string(body) != payload {
t.Fatalf("body = %q, want %q", string(body), payload)
}
}
func TestGetJSONAcceptsBodyAtLimit(t *testing.T) {
payload := `{"symbol":"BTCUSDT","price":"64385.12"}`
payload += strings.Repeat(" ", maxResponseBodyBytes-len(payload))
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "application/json")
w.Write([]byte(payload))
}))
defer server.Close()
body, err := getJSON(context.Background(), server.Client(), server.URL)
if err != nil {
t.Fatal(err)
}
if string(body) != payload {
t.Fatalf("body = %q, want %q", string(body), payload)
}
}
🤖 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 `@token-price-oracle/client/cex_feed_test.go` around lines 118 - 133, Update
TestGetJSONAcceptsBodyAtLimit so the valid JSON payload is padded with trailing
whitespace using strings.Repeat until its length is exactly
maxResponseBodyBytes; add the strings import if needed, while preserving the
existing request and body-equality assertions.

extractInnerTxFullBytes sized the full-tx buffer with
1+uint32(sizeByteLen)+size, which wraps when the declared RLP size is
near MaxUint32 (e.g. a 0xffffffff length prefix), producing a buffer
shorter than the slice copies below and panicking. The remaining-length
guard added in #1028 already bounds size to the available input on the
batch-decode path, so this only wraps for an out-of-band reader; computing
the length in uint64 removes the panic unconditionally as defense in depth.

Co-authored-by: Cursor <cursoragent@cursor.com>
@curryxbo curryxbo changed the title fix(token-price-oracle): bound HTTP price response body size fix: Aug upgrade review follow-ups (oracle response body bound + RLP alloc uint64) Aug 11, 2026
@curryxbo curryxbo changed the title fix: Aug upgrade review follow-ups (oracle response body bound + RLP alloc uint64) fix: bound token-price-oracle response body and RLP full-tx buffer length Aug 11, 2026
corey and others added 2 commits August 11, 2026 10:46
The previous change widened the buffer arithmetic to uint64, which removes
the overflow panic only because size happens to be a uint32; it leaves the
real risk untouched. The declared size still drives make([]byte, size), and
the remaining-length guard that should cap it was conditional -- skipped
entirely when the reader does not expose Len().

Make the bound explicit and fail-closed instead: require a reader that
reports its remaining length (every decode path funnels through
DecodeTxsFromBytes with a *bytes.Reader) and reject a declared size that
exceeds it. With size bounded by the actual remaining input, the allocation
can no longer be attacker-controlled and the length arithmetic is a small
in-range value by construction, independent of the width of size's type.

Co-authored-by: Cursor <cursoragent@cursor.com>
End-to-end analysis shows the uint32 length wrap at blob.go is unreachable:
the compressed batch input is hard-capped by the L1 blobs-per-tx limit, the
#1028 size>remaining guard already bounds the declared length to the
decompressed stream, and reaching a ~4.29 GiB stream would OOM inside zstd
decompression before the RLP decoder runs. The #1028 guard on main is the
sufficient defense; the extra hardening addressed a condition the real data
flow cannot produce, so revert it and keep this PR to the oracle fix.

Co-authored-by: Cursor <cursoragent@cursor.com>
@curryxbo curryxbo changed the title fix: bound token-price-oracle response body and RLP full-tx buffer length fix(token-price-oracle): bound HTTP price response body size Aug 11, 2026
size is a uint32, so 1+sizeByteLen+size can exceed MaxUint32 and wrap to a
tiny buffer length, leaving fullTxBytes shorter than the copies that follow
and panicking. The #1028 remaining-bytes guard keeps this unreachable on the
current decode path, but compute the length in uint64 and reject the
overflow before allocating so the decoder stays safe if the size type or the
upstream length bounds ever change.

Co-authored-by: Cursor <cursoragent@cursor.com>
@curryxbo curryxbo changed the title fix(token-price-oracle): bound HTTP price response body size fix: bound oracle response body; reject RLP tx length overflowing uint32 Aug 11, 2026
MetricsPort is a uint64 with no upper bound, so a misconfigured value
(e.g. >65535) produced an invalid listen address whose metrics
ListenAndServe failed silently in the background, logging only. Reject a
port outside 1..65535 in SetCliContext so the misconfig fails at startup.
The default (26660, matching Tendermint's instrumentation port) is always
in range, so valid configs are unaffected.

Co-authored-by: Cursor <cursoragent@cursor.com>
@curryxbo curryxbo changed the title fix: bound oracle response body; reject RLP tx length overflowing uint32 fix: oracle response-body bound, RLP length overflow guard, metrics port validation Aug 11, 2026
@curryxbo

Copy link
Copy Markdown
Contributor Author

Superseded by #1034, which regroups these review-driven hardening fixes under a properly scoped branch (not oracle-specific) with a clean one-commit-per-fix history.

@curryxbo curryxbo closed this Aug 11, 2026
@curryxbo
curryxbo deleted the fix/token-price-oracle-bound-response-body branch August 11, 2026 03:26
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