Skip to content

fix: validate untrusted input before indexing, asserting or dereferencing - #297

Merged
fbsobreira merged 8 commits into
masterfrom
fix/harden-untrusted-input
Aug 5, 2026
Merged

fix: validate untrusted input before indexing, asserting or dereferencing#297
fbsobreira merged 8 commits into
masterfrom
fix/harden-untrusted-input

Conversation

@fbsobreira

@fbsobreira fbsobreira commented Jul 30, 2026

Copy link
Copy Markdown
Owner

Wave 1 of the repository review: twelve findings that share one defect class — data from the node,
from a keystore file, or from a caller reaching an index, a type assertion, or a pointer dereference
with no check.

pkg/client — node responses

C8 ParseTRC20StringProperty took the ABI string length from contract return data and evaluated 2*int(l), which overflows to a negative value, passed the bounds check, then panicked on the slice.
W11 Four TRC20 queries indexed GetConstantResult()[0] unguarded; a node may report success with no entries.
W12 TRC20Send/Approve/TransferFrom encoded amounts with LeftPadBytes.
W27 DeployContractCtx assigned through tx.Transaction.RawData without checking the result code.
W28 UpdateAccountPermissionCtx made eight unchecked assertions on caller-supplied maps.
W43 GetTransactionInfoByIDCtx dereferenced a possibly-nil response.
W44 WithdrawExpireUnfreeze, DelegateResource, UnDelegateResource skipped the result-code check every sibling performs.

C8 had two failure modes, not one

Verified by running the original bounds check against each input:

Declared length Old behaviour
2^62 panicslice bounds [:9223372036854775936]
2^63 silent ""2*l wraps to exactly 0
max uint64 panic
2^254 silent ""Uint64 truncates to the low bits
max uint256 panic

A hostile name() could therefore return an empty token name with a nil error, not only crash. Now
IsUint64 catches the truncation and the bound is expressed as a division, so no multiplication exists
to overflow.

W12 encoding is unchanged for valid amounts

All three sites now use trc20enc.PadUint256. Confirmed byte-identical to LeftPadBytes across
0, 1, 1e6, max uint64, 2^255−1 and max uint256 — zero mismatches, so calldata for any amount that
worked before is untouched. What changes is that nil (panicked), negative (silently became a real
positive transfer, because big.Int.Bytes returns the absolute value) and >256-bit values (appended
whole, because LeftPadBytes does not truncate) are now rejected.

Five further sites of the same class

Found while reviewing the above. triggerConstantContract, triggerContract, DeployContract and
their callers in contracts.go, plus TRC20CallCtx, all read tx.Result.Code directly rather
than through the nil-safe accessor, so a response with no Result panicked. TRC20CallCtx gates all
four W11 queries — the constant-result indexing was guarded while a nil-Result panic sat upstream on
the same path. triggerContract also assigned through tx.Transaction.RawData with no nil check, the
same shape as W27.

pkg/common/numeric

  • W37 — unanchored regex, so "1e5x" matched, Atoi("5x") failed silently, and the function
    returned 1 with a nil error. Anchored; both errors propagate.
  • W38 — a nil *big.Int from SetString reached Mul and panicked. It also accepted a sign
    (because big.Int.SetString does), yielding a negative Dec out of a hex parser.

pkg/keystore

  • W10getKDFKey asserted salt/prf to string and every numeric parameter to float64 with no
    comma-ok, and passed n, r, p to scrypt with no upper bound — all before the MAC is
    verified. A crafted file could panic the process or force an unbounded derivation. Bounds leave room
    for StandardScryptN (256 MiB working set) and pbkdf2 defaults.
  • W26NewKeyForDirectICAP retried until an address began with 0x00, which a TRON address never
    does, so every call recursed until the stack overflowed. Removed; ICAP has no meaning on TRON.

Testing

W66 adds the negative-case coverage whose absence let W37 and W38 survive.

Tests assert both that input is rejected and that nothing panics. Branches a real gRPC round trip
cannot produce — a nil TransactionInfo, a nil Result — are reached by stubbing the exported Client
field, which is also how an SDK consumer can produce them. (grpc-go materialises a (nil, nil) server
return into an empty message, so a mock server cannot reach those guards; coverage confirmed the branch
was dead before this change.)

Every touched function keeps its pre-existing happy-path test. Coverage: pkg/client 83.5 → 84.7%,
numeric 91.7 → 92.9%, keystore 84.0 → 86.1%.

make lint (0 issues) · make test (20/20 packages) · go test ./cmd/... · make — all clean.

Not included

W42 (nil g.Client before Start) is a mechanical sweep of 80 call sites across 13 files and
does not belong in the same diff as these targeted guards. It gets its own PR.

⚠️ Behaviour change for release notes

W27 and W44 change observable behaviour. Callers that previously received a TransactionExtention
for a request the node rejected now receive an error. That is the fix — the failure previously
surfaced only after signing and broadcast, or not at all — but it is breaking for anyone who was
ignoring the result code.

Summary by CodeRabbit

  • Bug Fixes
    • Improved detection and reporting of node-rejected transactions across account, asset, bank, contract, exchange, proposal, resource, transfer, and witness operations.
    • Prevented crashes on empty, nil, or malformed responses, permissions, transaction lookups, and TRC20 calls.
    • Hardened numeric parsing and TRC20 decoding against overflow, underflow, and malformed input.
    • Strengthened keystore decryption by validating cipher, KDF, and encryption parameters.
  • Breaking Changes
    • Removed the Direct ICAP key-generation helper.
  • Tests
    • Added extensive regression coverage for malformed inputs and rejected responses.

…cing

Wave 1 of the repository review: twelve findings that share one defect
class — data from the node, from a keystore file or from a caller
reaching an index, a type assertion or a pointer dereference with no
check.

pkg/client, node responses:

C8  ParseTRC20StringProperty took the ABI string length from contract
    return data and evaluated 2*int(l), which overflows to a negative
    value and passed the bounds check before panicking on the slice.
    A >64-bit length was also silently truncated by Uint64 to its low
    bits, returning "" with a nil error. Reject non-uint64 lengths and
    bound by division so no multiplication can overflow.
W11 Four TRC20 queries indexed GetConstantResult()[0] unguarded; a node
    may report success with no entries. Routed through one helper, which
    also requires a full 32-byte ABI word, matching callForAddress.
W12 TRC20 transfer, approve and transferFrom encoded amounts with
    LeftPadBytes: nil panicked, a negative amount became a real positive
    transfer because big.Int.Bytes returns the absolute value, and a
    >256-bit value was appended whole because LeftPadBytes does not
    truncate. All three now use the validated trc20enc.PadUint256, which
    is byte-identical for every previously valid amount.
W27 DeployContractCtx assigned to tx.Transaction.RawData without
    checking the result code, so a node-side rejection panicked instead
    of reporting why the deployment failed.
W28 UpdateAccountPermissionCtx made eight unchecked assertions on
    caller-supplied maps. A missing key or an int where int64 was meant
    panicked mid-update.
W43 GetTransactionInfoByIDCtx dereferenced a possibly-nil response.
W44 WithdrawExpireUnfreeze, DelegateResource and UnDelegateResource
    skipped the result-code check every sibling performs, so rejections
    surfaced only after signing and broadcast.

Five further sites of the same class, found while reviewing the above:
triggerConstantContract, triggerContract, DeployContract and their
callers in contracts.go, plus TRC20CallCtx, all read tx.Result.Code
directly rather than through the nil-safe accessor, so a response with
no Result panicked. TRC20CallCtx gates all four W11 queries, and
triggerContract assigned through tx.Transaction.RawData with no nil
check — the same shape as W27. All now use GetResult()/GetTransaction().

pkg/common/numeric:

W37 NewDecFromString used an unanchored regex, so "1e5x" matched and
    Atoi("5x") failed silently, returning 1 with a nil error. Anchored,
    and both errors now propagate.
W38 NewDecFromHex let a nil big.Int from SetString reach Mul, panicking.
    It also accepted a sign, because big.Int.SetString does, yielding a
    negative Dec out of a hex parser; signs are now rejected.

pkg/keystore:

W10 getKDFKey asserted salt and prf to string and every numeric
    parameter to float64 with no comma-ok, and passed n, r and p to
    scrypt with no upper bound — all before the MAC is verified, so a
    crafted file could panic the process or force an unbounded
    derivation. Bounds leave room for StandardScryptN and pbkdf2
    defaults.
W26 NewKeyForDirectICAP retried until an address began with "0x00",
    which a TRON address never does, so every call recursed until the
    stack overflowed. Removed; ICAP has no meaning on TRON.

W66 Negative-case coverage for the numeric parsers, which is why W37 and
    W38 survived.

W42 (nil g.Client before Start) is deliberately not here: it is a
mechanical sweep of 80 call sites across 13 files and does not belong in
the same diff as these targeted guards.

Tests assert both that the input is rejected and that nothing panics.
The TRC20 length cases were verified against the original code to
confirm which panic and which returned a silent empty string. Branches
that a real gRPC round trip cannot produce — a nil TransactionInfo, a
nil Result — are reached by stubbing the exported Client field, which is
also how an SDK consumer can produce them.

Note for the release notes: W27 and W44 change observable behaviour.
Callers that previously received a TransactionExtention for a request
the node rejected now receive an error.
@coderabbitai

coderabbitai Bot commented Jul 30, 2026

Copy link
Copy Markdown

Review Change Stack

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

The PR hardens client RPC response handling, permission parsing, TRC20 ABI processing, numeric conversion, and keystore decryption. It also removes the exported Direct ICAP key-generation helper and adds malformed-input and node-response tests.

Changes

Client input and response validation

Layer / File(s) Summary
Permission field validation
pkg/client/account.go, pkg/client/untrusted_input_test.go
Permission maps now validate required fields and runtime types before permission construction.
Transaction response validation
pkg/client/{account,assets,bank,contracts,exchange,network,proposal,resources,transfer,witnesses,txext}.go, pkg/client/*test.go
Client methods now use centralized transaction-extension validation and handle nil responses, rejected node results, and missing transaction data.
TRC20 ABI and amount validation
pkg/client/trc20.go, pkg/client/trc20_test.go, pkg/client/untrusted_input_test.go
TRC20 calls validate constant results, use overflow-safe string bounds, and reject invalid amounts. Tests cover malformed ABI responses and lengths.

Numeric parser validation

Layer / File(s) Summary
Decimal and hexadecimal parsing
pkg/common/numeric/numeric.go, pkg/common/numeric/parse_malformed_test.go
Decimal scientific notation now requires complete matches and checked conversions. Hexadecimal parsing validates malformed input and integer conversion results.

Keystore input and key generation changes

Layer / File(s) Summary
Cipher and KDF input validation
pkg/keystore/passphrase.go, pkg/keystore/crypto.go, pkg/keystore/*_test.go
Keystore decryption validates cipher fields, AES parameters, KDF types, KDF ranges, and resource limits before cryptographic work.
Direct ICAP generator removal
pkg/keystore/key.go, pkg/keystore/key_test.go
The exported NewKeyForDirectICAP function, its unused imports, and its explanatory test comment are removed.

Estimated code review effort: 4 (Complex) | ~60 minutes

Possibly related PRs

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 68.85% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly summarizes the PR's main change: validating untrusted input before unsafe indexing, assertions, and dereferences.
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.
✨ Finishing Touches 💡 1
📝 Generate docstrings 💡
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/harden-untrusted-input

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

@codecov

codecov Bot commented Jul 30, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 95.83333% with 13 lines in your changes missing coverage. Please review.
✅ Project coverage is 82.77%. Comparing base (e196fce) to head (e48651a).

Files with missing lines Patch % Lines
pkg/common/numeric/numeric.go 87.17% 3 Missing and 2 partials ⚠️
pkg/keystore/passphrase.go 95.74% 2 Missing and 2 partials ⚠️
pkg/client/proposal.go 66.66% 1 Missing and 1 partial ⚠️
pkg/client/witnesses.go 75.00% 1 Missing and 1 partial ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##           master     #297      +/-   ##
==========================================
+ Coverage   80.92%   82.77%   +1.85%     
==========================================
  Files          76       77       +1     
  Lines        6201     6333     +132     
==========================================
+ Hits         5018     5242     +224     
+ Misses        844      785      -59     
+ Partials      339      306      -33     
Files with missing lines Coverage Δ
pkg/client/account.go 83.19% <100.00%> (+4.98%) ⬆️
pkg/client/assets.go 95.42% <100.00%> (-0.25%) ⬇️
pkg/client/bank.go 77.95% <100.00%> (+6.10%) ⬆️
pkg/client/contracts.go 75.27% <100.00%> (+2.18%) ⬆️
pkg/client/exchange.go 88.46% <100.00%> (+11.67%) ⬆️
pkg/client/network.go 82.05% <100.00%> (+0.23%) ⬆️
pkg/client/resources.go 70.42% <100.00%> (+0.85%) ⬆️
pkg/client/transfer.go 89.47% <100.00%> (-1.01%) ⬇️
pkg/client/trc20.go 87.81% <100.00%> (+1.45%) ⬆️
pkg/client/txext.go 100.00% <100.00%> (ø)
... and 6 more
🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@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: 3

🤖 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 `@pkg/client/trc20.go`:
- Around line 98-103: Update the rejected-result branch in the relevant TRC20
contract method to return a nil result together with the formatted error,
matching triggerContract and DeployContractCtx. Preserve returning the node
result only on successful execution so TRC20Send, TRC20Approve, and
TRC20TransferFrom never expose a rejected TransactionExtention alongside an
error.

In `@pkg/common/numeric/numeric.go`:
- Around line 672-676: In pkg/common/numeric/numeric.go:672-676, validate the
parsed exponent before calling Pow, rejecting values outside the established
safe bound, including the minimum signed int, and return the existing
invalid-exponent error path. In
pkg/common/numeric/parse_malformed_test.go:15-28, add malformed-input cases
covering the minimum signed int exponent and an exponent above the safe limit,
asserting both are rejected without invoking unbounded exponentiation.

In `@pkg/keystore/passphrase.go`:
- Around line 394-397: Update the kdfInt validation for "dklen" in
getKDFKey/DecryptDataV3 to use a minimum of 32, matching the AES and MAC key
slices derived from the result. Preserve the existing maximum bound and error
propagation, and add a kdfparams test case covering a dklen below 32 alongside
the existing upper-bound test.
🪄 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: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: c5dbedf8-93a5-4f61-a253-6b4616f38c46

📥 Commits

Reviewing files that changed from the base of the PR and between e196fce and 80b8a03.

📒 Files selected for processing (14)
  • pkg/client/account.go
  • pkg/client/bank.go
  • pkg/client/contracts.go
  • pkg/client/network.go
  • pkg/client/resources.go
  • pkg/client/trc20.go
  • pkg/client/trc20_test.go
  • pkg/client/untrusted_input_test.go
  • pkg/common/numeric/numeric.go
  • pkg/common/numeric/parse_malformed_test.go
  • pkg/keystore/kdfparams_test.go
  • pkg/keystore/key.go
  • pkg/keystore/key_test.go
  • pkg/keystore/passphrase.go
💤 Files with no reviewable changes (2)
  • pkg/keystore/key_test.go
  • pkg/keystore/key.go

Comment thread pkg/client/trc20.go Outdated
Comment thread pkg/common/numeric/numeric.go Outdated
Comment thread pkg/keystore/passphrase.go Outdated
…rejects

Three findings from review of the previous commit.

NewDecFromString accepted any exponent the anchored regex matched, and
Atoi accepts the whole int range. Pow raises 10 to it, which panics with
"Int overflow" past 10^76 because Dec is capped at 255+DecimalPrecisionBits
bits over an 18-decimal scale; and Pow negates a negative exponent, so
math.MinInt stays negative and recurses until the stack overflows.
"1e-9223372036854775808" was a fatal stack overflow from a plain string.
Exponents are now bounded to the representable range, verified empirically
as 10^76 representable and 10^77 panicking.

getKDFKey allowed dklen down to 1, but the decrypt paths read
derivedKey[:16] for the AES key and derivedKey[16:32] for the MAC. That
does not panic today only because scrypt.Key and pbkdf2.Key return a slice
with capacity 32 even when dklen is 1, so the expression reads past len
into spare capacity. Require the V3 spec's 32 and stop depending on that.

TRC20CallCtx returned the node's rejection payload alongside the error,
unlike triggerContract and DeployContractCtx which return nil. TRC20Send,
TRC20Approve and TRC20TransferFrom pass that value straight through, so a
caller checking the result before the error could sign and broadcast a
rejected transaction. Returns nil now.
@fbsobreira

Copy link
Copy Markdown
Owner Author

All three addressed in 3fe12842. Each was verified against the code before fixing — one did not reproduce as described, so noting that explicitly.

✅ Exponent bound — confirmed, and worse than reported

Real, and it is a hard crash from a plain string. NewDecFromString("1e-9223372036854775808"):

runtime: goroutine stack exceeds 1000000000-byte limit
fatal error: stack overflow

Your analysis of the mechanism is exactly right: Pow negates a negative exponent and math.MinInt stays negative under negation.

There is a second overflow you did not mention, which I hit because my first bound was still too loose. I picked ±1000 and my own test case 1e1000 panicked with Int overflowDec is capped at 255+DecimalPrecisionBits bits over an 18-decimal scale. So the safe range is much narrower. Measured it rather than computed it:

largest safe positive exponent: 76 (10^76 ok, 10^77 panics)

Bounded to ±76. The original input now returns exponent -9223372036854775808 in "1e-9223372036854775808" outside [-76, 76].

⚠️ dklen — the panic does not reproduce, but fixed anyway

I could not reproduce the slice-bounds panic. A keystore with "dklen": 1 returns a clean could not decrypt key with given passphrase, not a crash.

derivedKey[16:32] is a slice expression, so it is legal whenever cap ≥ 32 — not len ≥ 32. Both KDFs round up to a whole hash block internally, so they return cap 32 even for dklen 1:

dklen= 1  scrypt len=1  cap=32 panics=false | pbkdf2 len=1  cap=32 panics=false
dklen=15  scrypt len=15 cap=32 panics=false | pbkdf2 len=15 cap=32 panics=false
dklen=31  scrypt len=31 cap=32 panics=false | pbkdf2 len=31 cap=32 panics=false

So it is not Critical and not a panic. But the underlying point stands: reading past len into spare capacity is fragile, it depends on an implementation detail of golang.org/x/crypto, and a sub-32 dklen is invalid per the V3 spec regardless. Minimum is now 32, which removes the dependency. EncryptDataV3 always writes scryptDKLen = 32, so round trips are unaffected.

✅ Non-nil result with error — confirmed

Correct on both counts. TRC20SendCtx, TRC20ApproveCtx and TRC20TransferFromCtx all return g.TRC20CallCtx(...) directly, so the rejection payload reaches the caller. Returns nil now, matching triggerContract and DeployContractCtx.

Worth flagging for release notes: a caller who inspected the returned extention on error to read a revert reason from a constant call loses that. No in-tree caller does — all four query methods return early on err != nil.

Tests

  • exponent: math.MinInt, math.MaxInt, ±1e5 overflow cases, 1e77/1e-77, plus 1e76 as a valid boundary case
  • dklen: 1, 31, and a pbkdf2 16

make lint 0 issues · make test 20/20 · go test ./cmd/... · make — all clean.

@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

🧹 Nitpick comments (2)
pkg/common/numeric/parse_malformed_test.go (2)

102-106: 🎯 Functional Correctness | 🔵 Trivial | ⚡ Quick win

Assert exact decoded values.

IsZero() only proves the result is non-zero; an incorrect value would still pass. Compare both 0xff and ff against the exact decimal result.

🤖 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 `@pkg/common/numeric/parse_malformed_test.go` around lines 102 - 106, Update
TestNewDecFromHex_Valid to assert that both numeric.NewDecFromHex("0xff") and
numeric.NewDecFromHex("ff") equal the exact decimal value 255, while preserving
the existing NotPanics coverage.

29-38: 🩺 Stability & Availability | 🔵 Trivial | ⚡ Quick win

Add regressions for allowed-but-unrepresentable inputs.

Cover 9e76 and a long syntactically valid hex value such as strings.Repeat("f", 128). Both should be rejected without panic after the implementation fix.

Also applies to: 77-100

🤖 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 `@pkg/common/numeric/parse_malformed_test.go` around lines 29 - 38, Extend the
malformed-input regression cases to include the allowed-but-unrepresentable
decimal input 9e76 and a syntactically valid 128-character hexadecimal value
generated with strings.Repeat. Ensure both cases are asserted as rejected and
processed without panicking, preserving the existing malformed-input test
behavior.
🤖 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 `@pkg/common/numeric/numeric.go`:
- Around line 692-695: Validate the total numeric magnitude before fixed-point
arithmetic in the scientific-notation parsing path around numeric.go:692-695,
rejecting values such as 9e76 before Mul or Pow can overflow. In the hexadecimal
parsing path at numeric.go:731-738, reject or short-circuit inputs whose length
would make Pow overflow. Add 9e76 to parse_malformed_test.go:29-38 as a no-panic
rejection case and add an oversized valid-hex regression case in
parse_malformed_test.go:77-100.

---

Nitpick comments:
In `@pkg/common/numeric/parse_malformed_test.go`:
- Around line 102-106: Update TestNewDecFromHex_Valid to assert that both
numeric.NewDecFromHex("0xff") and numeric.NewDecFromHex("ff") equal the exact
decimal value 255, while preserving the existing NotPanics coverage.
- Around line 29-38: Extend the malformed-input regression cases to include the
allowed-but-unrepresentable decimal input 9e76 and a syntactically valid
128-character hexadecimal value generated with strings.Repeat. Ensure both cases
are asserted as rejected and processed without panicking, preserving the
existing malformed-input test behavior.
🪄 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: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: c825e7be-f330-457d-bb1b-f2a6dd32ceb5

📥 Commits

Reviewing files that changed from the base of the PR and between 80b8a03 and 3fe1284.

📒 Files selected for processing (5)
  • pkg/client/trc20.go
  • pkg/common/numeric/numeric.go
  • pkg/common/numeric/parse_malformed_test.go
  • pkg/keystore/kdfparams_test.go
  • pkg/keystore/passphrase.go

Comment thread pkg/common/numeric/numeric.go Outdated
The exponent bound added in the previous commit is necessary but not
sufficient. Magnitude depends on the mantissa as well, so 1e76 is
representable while 9e76 and 99e75 sit inside the bound and still panic
with "Int overflow". NewDecFromHex has the same problem by length: a
64-character string — an ordinary ABI uint256 word — exceeds Dec's range
and panicked, as did anything longer.

Deciding in advance whether a parsed value fits means reproducing Dec's
255+DecimalPrecisionBits cap in the parser, which is where the previous
two attempts went wrong. Instead both parsers recover the documented
"Int overflow" panic and convert it to ErrOutOfRange, re-raising anything
else so real bugs still surface. That covers every arithmetic path exactly
rather than approximating it.

The exponent bound stays: it stops math.MinInt reaching Pow, where the
negation of the minimum signed int is still negative and recurses until
the stack overflows, which no recover can turn into an error.

NewDecFromHex keeps its signature and degrades to zero, consistent with
how it already reports other invalid input.
@fbsobreira

Copy link
Copy Markdown
Owner Author

Both confirmed and fixed in 9477ab60. The magnitude point was right and I had it wrong twice.

9e76 — confirmed

The exponent bound I added was necessary but not sufficient, exactly as you said. Magnitude depends on the mantissa too:

1e76   -> ok        5e76   -> ok        1.5e76 -> ok
9e76   -> PANIC: Int overflow
99e75  -> PANIC: Int overflow

Hex — confirmed, and worse than the report

You flagged "lengths that would overflow Pow". Measured, the threshold is 64 characters — an ordinary ABI uint256 word:

16 chars -> ok
32 chars -> ok
64 chars -> PANIC: Int overflow
200 chars -> PANIC: Int overflow

So the most natural input to a hex→decimal helper crashed.

Fix

I tried computing the limit in the parser twice and got it wrong both times — first ±1000 (my own test case 1e1000 panicked), then ±76, which 9e76 walked straight through. Reproducing Decs 255+DecimalPrecisionBits cap outside Dec is the wrong approach.

Both parsers now recover the documented "Int overflow" panic and convert it to ErrOutOfRange, re-raising anything else so real bugs still surface. That covers every arithmetic path exactly instead of approximating it.

The exponent bound stays, for the one case recover cannot help with: math.MinInt reaching Pow, where negating the minimum signed int leaves it negative and it recurses until the stack overflows.

9e76        -> error: value out of representable range
99e75       -> error: value out of representable range
64 chars    -> 0.000000000000000000
1e76, 5e76, 1.5e76, 32-char hex -> still parse correctly

NewDecFromHex keeps its signature and degrades to zero, consistent with how it already reports other invalid input.

One correction to my own test: I initially added 12345e72 as an overflow case. It is 1.2345e76, which is representable — the test caught it and it is removed.

make lint 0 issues · make test 20/20 · go test ./cmd/... · make — all clean.

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

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
pkg/common/numeric/numeric.go (1)

732-732: 🩺 Stability & Availability | 🟠 Major | ⚡ Quick win

Validate non-scientific values against Dec’s range.

This fallback only constructs a big.Int, so recoverIntOverflow sees no panic. A plain value such as strings.Repeat("9", 77) is accepted with no error despite its precision-scaled representation exceeding Dec’s range; later arithmetic can still panic. Validate this branch (preferably centrally in NewDecFromStr) and add a regression asserting ErrOutOfRange.

🤖 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 `@pkg/common/numeric/numeric.go` at line 732, The non-scientific fallback
around NewDecFromStr accepts values whose precision-scaled representation
exceeds Dec’s range. Update NewDecFromStr or the fallback caller to validate the
constructed value against Dec bounds and return ErrOutOfRange, ensuring
scientific and non-scientific inputs share the same validation; add a regression
covering an oversized repeated-9 input.
🤖 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.

Outside diff comments:
In `@pkg/common/numeric/numeric.go`:
- Line 732: The non-scientific fallback around NewDecFromStr accepts values
whose precision-scaled representation exceeds Dec’s range. Update NewDecFromStr
or the fallback caller to validate the constructed value against Dec bounds and
return ErrOutOfRange, ensuring scientific and non-scientific inputs share the
same validation; add a regression covering an oversized repeated-9 input.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 408bee2c-e848-4c24-8bbb-436674442917

📥 Commits

Reviewing files that changed from the base of the PR and between 3fe1284 and 9477ab6.

📒 Files selected for processing (2)
  • pkg/common/numeric/numeric.go
  • pkg/common/numeric/parse_malformed_test.go

coderabbitai[bot]
coderabbitai Bot previously approved these changes Jul 30, 2026
…t tests

Require non-nil RawData after a success code on Delegate, UnDelegate and
WithdrawExpireUnfreeze so a hollow TransactionExtention cannot be signed.
Standardise GetCode() != 0, restore UpdateAccountPermissionCtx godoc, and
document TRC20CallCtx's constant-path Result tolerance.

Extend untrusted-input tests for rejection and success-without-tx on those
builders plus TriggerContract/TRC20Send. Add DecryptKey compatibility cases
for in-the-wild KDF params and the intentional short-dklen rejection.
Introduce requireTxExtension for every write builder so SUCCESS responses
without RawData cannot be returned as signable transactions.

Reject plain decimal overflow in NewDecFromStr and scientific underflows
that collapse to zero at fixed-point precision. Cap keystore KDF salt
length before hex-decode so unauthenticated params cannot allocate
unbounded input pre-MAC.
Validate MAC, IV and ciphertext length before KDF/decrypt so crafted
keystore JSON cannot panic NewCTR/CryptBlocks or force unbounded
pre-MAC allocations. Reject empty estimate-energy and GetAccount
responses from substituted clients, and include result codes when
node rejection messages are empty.

@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: 2

🧹 Nitpick comments (1)
pkg/client/untrusted_input_test.go (1)

168-258: 🗄️ Data Integrity & Integration | 🔵 Trivial | ⚡ Quick win

Assert that invalid responses return no transaction.

These cases discard tx. A regression from return nil, err to return response, err would pass the tests while callers receive a rejected extension. Capture tx and require nil in each rejection and no-transaction case.

Proposed test change
- _, err := c.WithdrawExpireUnfreeze(testAddrA, 1700000000000)
+ tx, err := c.WithdrawExpireUnfreeze(testAddrA, 1700000000000)
  require.ErrorContains(t, err, "node refused the request")
+ require.Nil(t, tx)
🤖 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 `@pkg/client/untrusted_input_test.go` around lines 168 - 258, Update
TestBuilders_SurfaceNodeRejection and TestDeployContract_NodeRejection to
capture the returned transaction alongside err in every rejection and
no-transaction subtest, then assert the transaction is nil while retaining the
existing error assertions and panic guards.
🤖 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 `@pkg/client/txext.go`:
- Line 22: Update the transaction validation around tx.GetResult() to reject a
nil Result before calling GetCode(), returning an error for responses with
RawData but no Result; preserve the existing nonzero-code validation and add a
regression test covering the absent-Result case.

In `@pkg/keystore/passphrase.go`:
- Around line 273-275: Update EncryptDataV3 to reject plaintext or resulting
payloads exceeding the existing 1,024-byte V3 ciphertext limit before performing
KDF work, matching DecryptDataV3’s maxCiphertextLen validation. Document this
limit alongside the V3 implementation and add boundary tests covering exactly
the maximum and one byte over it.

---

Nitpick comments:
In `@pkg/client/untrusted_input_test.go`:
- Around line 168-258: Update TestBuilders_SurfaceNodeRejection and
TestDeployContract_NodeRejection to capture the returned transaction alongside
err in every rejection and no-transaction subtest, then assert the transaction
is nil while retaining the existing error assertions and panic guards.
🪄 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: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 1878ec26-94b5-4c23-900e-a3cf153b96c0

📥 Commits

Reviewing files that changed from the base of the PR and between 9477ab6 and beaf955.

📒 Files selected for processing (20)
  • pkg/client/account.go
  • pkg/client/assets.go
  • pkg/client/bank.go
  • pkg/client/contracts.go
  • pkg/client/exchange.go
  • pkg/client/proposal.go
  • pkg/client/resources.go
  • pkg/client/transfer.go
  • pkg/client/trc20.go
  • pkg/client/txext.go
  • pkg/client/txext_test.go
  • pkg/client/untrusted_input_test.go
  • pkg/client/witnesses.go
  • pkg/common/numeric/numeric.go
  • pkg/common/numeric/parse_malformed_test.go
  • pkg/keystore/cipherparams_test.go
  • pkg/keystore/compat_test.go
  • pkg/keystore/crypto.go
  • pkg/keystore/kdfparams_test.go
  • pkg/keystore/passphrase.go
🚧 Files skipped from review as they are similar to previous changes (3)
  • pkg/client/contracts.go
  • pkg/keystore/kdfparams_test.go
  • pkg/client/trc20.go

Comment thread pkg/client/txext.go
Comment thread pkg/keystore/passphrase.go
Cover requireTxExtension empty-op paths, estimateEnergy rejections,
SUCCESS-without-transaction across more write builders, AES IV/block
alignment errors, invalid cipher hex, and kdfInt int/int64 branches so
Codecov patch coverage clears the 80% target.
requireTxExtension now errors when Result is absent before GetCode(),
which otherwise defaults to SUCCESS on a nil *Return. EncryptDataV3
enforces the same maxCiphertextLen as DecryptDataV3 before KDF work.

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

🧹 Nitpick comments (1)
pkg/client/untrusted_input_test.go (1)

128-147: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Assert that malformed permission maps do not reach the RPC.

The assertion at Lines 150-155 accepts any error. It can pass if AccountPermissionUpdate receives a malformed permission and the mock RPC returns an error. Count calls to AccountPermissionUpdateFunc, then assert zero for every case.

As per path instructions, pkg/client/** requires focus on error handling and API correctness.

🤖 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 `@pkg/client/untrusted_input_test.go` around lines 128 - 147, Update the
malformed permission cases in the untrusted-input test to count invocations of
AccountPermissionUpdateFunc and assert that each case makes zero RPC calls,
rather than accepting any returned error. Keep the existing validation-error
assertions while ensuring malformed active and witness permission maps are
rejected before reaching the RPC.

Source: Path instructions

🤖 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.

Nitpick comments:
In `@pkg/client/untrusted_input_test.go`:
- Around line 128-147: Update the malformed permission cases in the
untrusted-input test to count invocations of AccountPermissionUpdateFunc and
assert that each case makes zero RPC calls, rather than accepting any returned
error. Keep the existing validation-error assertions while ensuring malformed
active and witness permission maps are rejected before reaching the RPC.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 87acb970-5f1d-4963-8424-d5f4bdfaf79d

📥 Commits

Reviewing files that changed from the base of the PR and between beaf955 and e48651a.

📒 Files selected for processing (8)
  • pkg/client/txext.go
  • pkg/client/txext_test.go
  • pkg/client/untrusted_input_test.go
  • pkg/keystore/cipherparams_test.go
  • pkg/keystore/crypto_internal_test.go
  • pkg/keystore/kdf_internal_test.go
  • pkg/keystore/key_test.go
  • pkg/keystore/passphrase.go
🚧 Files skipped from review as they are similar to previous changes (2)
  • pkg/client/txext.go
  • pkg/keystore/passphrase.go

@fbsobreira
fbsobreira merged commit 3a24b60 into master Aug 5, 2026
8 checks passed
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