fix(mcp_impl): reject balance amounts beyond the safe JSON integer range - #178
fix(mcp_impl): reject balance amounts beyond the safe JSON integer range#178ascandone wants to merge 1 commit into
Conversation
The evaluate tool's balances now go through BindArguments into a typed *big.Int, which rejects fractional amounts but not magnitude loss: the MCP transport decodes incoming JSON numbers into a generic float64 before this handler ever runs, so an amount past 2^53-1 is already silently rounded by the time it reaches *big.Int's JSON unmarshaling - at that point it looks like a perfectly valid, exact integer. Reject any amount outside ±(2^53-1) before it's converted, instead of silently executing with a possibly-corrupted balance.
🛑 Changes requested — automated reviewThe new guard does not fully prevent precision-corrupted amounts from being accepted after float64 decoding, leaving an edge case where invalid values round into the accepted range. |
NumaryBot
left a comment
There was a problem hiding this comment.
NumaryBot posted 1 new inline finding.
Summary: #178 (comment)
| continue | ||
| } | ||
|
|
||
| amount, ok := row["amount"].(float64) |
There was a problem hiding this comment.
🟠 [major] Reject rounded fractional amounts near the safe boundary
For JSON numbers just over the safe bound that round back inside it, such as 9007199254740991.1 decoding to the float64 value 9007199254740991, this comparison passes and BindArguments then accepts the rounded integer as a big.Int. Because the original token is already lost here, this still allows silently corrupted out-of-range/fractional balances; the check needs access to the raw JSON/json.Number value or another conservative rejection strategy before binding.
|
No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info⚙️ Run configurationConfiguration used: Organization UI Review profile: CHILL Plan: Pro Plus Run ID: 📒 Files selected for processing (2)
WalkthroughThe MCP evaluation handler now validates raw balance amounts before binding. It rejects values outside the JSON safe integer range and preserves downstream rejection of fractional amounts. Tests cover positive, negative, valid, and fractional inputs. ChangesSafe balance validation
Estimated code review effort: 3 (Moderate) | ~20 minutes Poem
🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✨ Finishing Touches 💡 1🛠️ Fix failing CI checks 💡
📝 Generate docstrings
🧪 Generate unit tests (beta)
Warning There were issues while running some tools. Please review the errors and either fix the tool's configuration or disable the tool if it's a critical failure. 🔧 golangci-lint (2.12.2)level=error msg="[linters_context] typechecking error: pattern ./...: directory prefix . does not contain main module or its selected dependencies" 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. Comment |
Supersedes #142 and #143 (closing those, see comments there for why they no longer apply as-is).
Background
#142/#143 pinned/fixed a float64-truncation bug in
parseBalancesJson, a manual JSON parser for theevaluateMCP tool's balances that no longer exists — it was removed when the tool switched torequest.BindArgumentsinto a typedinterpreter.Balances(withAmount *big.Int).That switch does fix the fractional-amount case:
math/big.Int's ownUnmarshalJSONrejects"100.9"outright.It does not fix the large-magnitude case, though — it just moved it. I confirmed by reproducing the actual request path: the MCP server decodes the incoming JSON-RPC message via
json.Unmarshal(message, &request)intoParams.Arguments any, and Go's default for JSON numbers intoanyis alwaysfloat64, regardless of magnitude. That happens beforeBindArgumentsever runs. So an amount like9007199254740993(one past float64's exact range, 2^53-1) is silently rounded to9007199254740992at the transport layer, and by the timeBindArgumentssees it, it looks like a perfectly clean, valid integer —big.Inthas no way to tell it was ever anything else.Fix
Added
checkBalanceAmountsInSafeRange, which inspects the raw (pre-BindArguments) float64 amounts viarequest.GetArguments()and rejects anything outside±(2^53-1), before the value is converted to*big.Intand the corruption becomes unobservable. Mirrors the bounds-check approach from the old #143, just applied at the current call site.Test plan
go test ./internal/mcp_impl/...— all existing tests pass, plus new coverage:json.Unmarshal(not just a hand-built Go struct) to match the real corruption path