Skip to content

fix(api): harden external input validation - #21

Open
0xbigapple wants to merge 5 commits into
developfrom
fix/api-input-hardening
Open

fix(api): harden external input validation#21
0xbigapple wants to merge 5 commits into
developfrom
fix/api-input-hardening

Conversation

@0xbigapple

@0xbigapple 0xbigapple commented Aug 19, 2026

Copy link
Copy Markdown
Owner

What does this PR do?

This PR hardens externally reachable API inputs before expensive or ambiguous processing:

  • validates contract-query addresses before database access and Base58Check formatting
  • limits string-form integer inputs before BigDecimal conversion
  • validates Permission_id exactly, rejects strings longer than 64 characters, and preserves existing supported numeric forms
  • limits shielded TRC20 amount strings before BigInteger conversion
  • validates shielded transfer spend/receive counts before serialization loops
  • rejects absent, blank, and malformed reward and brokerage addresses consistently across FullNode, Solidity, and PBFT HTTP APIs
  • returns bounded validation messages without echoing oversized attacker-controlled values

Why are these changes required?

Malformed inputs could previously trigger disproportionate CPU work, silently truncate or wrap numeric values, or return plausible results for structurally invalid addresses.

These changes reject invalid inputs before expensive conversion, encoding, storage access, or repeated buffer merging.

Compatibility notes:

  • breaking: getReward and getBrokerage no longer substitute a default for an address they never received. Util.getAddress now either returns a valid address or throws, so a request that carries no address at all is rejected like any other unusable one: GET /wallet/getReward without the parameter answered {"reward": 0} and now answers {"Error":"INVALID address"}. The same applies to getBrokerage and to both endpoints on /walletsolidity and the PBFT mirror, six in total. A caller that relied on the zero default has to send an address.
  • other valid inputs retain their existing behavior
  • Permission_id values with fractions, integer overflow, explicit null, or string representations longer than 64 characters are rejected
  • shielded TRC20 APIs remain controlled by the existing node.allowShieldedTransactionApi flag, which is disabled by default
  • no protocol, protobuf, storage, dependency, configuration, or HTTP status-code changes are introduced

This PR has been tested by:

  • Unit Tests
  • Manual Testing

Follow up

Extra details


Summary by cubic

Hardens externally reachable API input validation and makes error responses consistent. getReward/getBrokerage (and their /walletsolidity/PBFT mirrors) no longer default missing addresses; they now require a valid address and return {"Error":"INVALID address"} instead of 0/20.

  • Bug Fixes

    • org.tron.core.services.http.Util#getAddress validates Base58Check and 41... hex, bounds body size while reading, and returns fixed errors “INVALID address”/“INVALID JSON body” without echoing input.
    • GetRewardServlet/GetBrokerageServlet use the unified handler so malformed requests are answered identically across FullNode/Solidity/PBFT.
    • org.tron.core.Wallet#getContract/#getContractInfo reject malformed addresses before any store access by returning null.
    • JSON numbers are capped at 64 characters; Permission_id must be an exact 32‑bit int and match across coercions, rejecting fractions, scientific forms, overflow, explicit null, and overlong strings; absent or ≤ 0 leaves the transaction unchanged.
    • Shielded TRC‑20: amount strings are capped at 80 characters; transfer requires 1–2 spends and 1–2 receives and, without ASK, a matching spend‑signature count; mint requires exactly one receive; unusable inputs surface as ContractValidateException (“invalid amount” or a specific argument error); the node.allowShieldedTransactionApi gate is unchanged.
    • No protocol, protobuf, storage, dependency, configuration, or HTTP status‑code changes.
  • Migration

    • Clients of /wallet/getReward and /wallet/getBrokerage (and /walletsolidity/PBFT mirrors) must send a valid address; previous zero/20 defaults are removed.

Written for commit 248cbe0. Summary will update on new commits.

Review in cubic

@chatgpt-codex-connector

Copy link
Copy Markdown

You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard.

@coderabbitai

coderabbitai Bot commented Aug 19, 2026

Copy link
Copy Markdown

Important

  • 🔍 Trigger review

This repository does not receive automatic reviews because it has fewer than 10 stars.

⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 8f3ea57f-e151-4df0-b055-5d76573bd5d1


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.

@cubic-dev-ai cubic-dev-ai 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.

1 issue found across 15 files

Prompt for AI agents (unresolved issues)

Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.


<file name="framework/src/test/java/org/tron/core/services/interfaceOnSolidity/http/RewardBrokerageAddressValidationTest.java">

<violation number="1" location="framework/src/test/java/org/tron/core/services/interfaceOnSolidity/http/RewardBrokerageAddressValidationTest.java:42">
P3: The Solidity and PBFT test files duplicate the entire harness (reflection-backed wallet factory, request builder, inject helper, and the reward/brokerage rejection test) differing only in the wallet type and injected field name. Extract a shared base class or parameterized harness to avoid maintaining the same assertions in two places.</violation>
</file>

Reply with feedback, questions, or to request a fix.

Re-trigger cubic

Comment thread framework/src/main/java/org/tron/core/Wallet.java Outdated
Comment thread framework/src/main/java/org/tron/core/services/http/Util.java Outdated
}

@Test
public void rewardAndBrokerageMirrorsRejectMalformedAddresses() throws Exception {

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P3: The Solidity and PBFT test files duplicate the entire harness (reflection-backed wallet factory, request builder, inject helper, and the reward/brokerage rejection test) differing only in the wallet type and injected field name. Extract a shared base class or parameterized harness to avoid maintaining the same assertions in two places.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At framework/src/test/java/org/tron/core/services/interfaceOnSolidity/http/RewardBrokerageAddressValidationTest.java, line 42:

<comment>The Solidity and PBFT test files duplicate the entire harness (reflection-backed wallet factory, request builder, inject helper, and the reward/brokerage rejection test) differing only in the wallet type and injected field name. Extract a shared base class or parameterized harness to avoid maintaining the same assertions in two places.</comment>

<file context>
@@ -0,0 +1,57 @@
+  }
+
+  @Test
+  public void rewardAndBrokerageMirrorsRejectMalformedAddresses() throws Exception {
+    GetRewardOnSolidityServlet reward = new GetRewardOnSolidityServlet();
+    inject(reward, "walletOnSolidity", walletOnSolidity());
</file context>

@cubic-dev-ai cubic-dev-ai 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.

2 issues found across 17 files (changes from recent commits).

Prompt for AI agents (unresolved issues)

Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.


<file name="framework/src/test/java/org/tron/core/services/http/AddressQueryServletTestBase.java">

<violation number="1" location="framework/src/test/java/org/tron/core/services/http/AddressQueryServletTestBase.java:71">
P3: When a test address contains JSON-significant characters, `jsonRequest` creates malformed JSON instead of encoding the address as a JSON string. Escape the value with the project's JSON serializer (or otherwise quote it correctly) so this shared contract helper exercises the endpoint with the intended address.</violation>
</file>

<file name="framework/src/main/java/org/tron/core/services/http/Util.java">

<violation number="1" location="framework/src/main/java/org/tron/core/services/http/Util.java:705">
P2: For multibyte UTF-8 bodies, this check counts characters instead of request bytes and buffers well beyond `httpMaxMessageSize` before rejecting. Count bytes from the input stream while reading so the early bound matches the configured limit.</violation>
</file>

Tip: Review your code locally with the cubic CLI to iterate faster.

Re-trigger cubic

new InputStreamReader(request.getInputStream()))) {
int read;
while ((read = reader.read(buffer)) != -1) {
if (sb.length() + read > limit) {

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2: For multibyte UTF-8 bodies, this check counts characters instead of request bytes and buffers well beyond httpMaxMessageSize before rejecting. Count bytes from the input stream while reading so the early bound matches the configured limit.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At framework/src/main/java/org/tron/core/services/http/Util.java, line 705:

<comment>For multibyte UTF-8 bodies, this check counts characters instead of request bytes and buffers well beyond `httpMaxMessageSize` before rejecting. Count bytes from the input stream while reading so the early bound matches the configured limit.</comment>

<file context>
@@ -673,14 +684,33 @@ private static String checkGetParam(HttpServletRequest request, String key) thro
+        new InputStreamReader(request.getInputStream()))) {
+      int read;
+      while ((read = reader.read(buffer)) != -1) {
+        if (sb.length() + read > limit) {
+          throw new Exception("body size is too big, the limit is " + limit);
+        }
</file context>


protected static MockHttpServletRequest jsonRequest(String address, String contentType) {
MockHttpServletRequest request = postRequest(contentType);
String json = address == null ? "{}" : "{\"address\":\"" + address + "\"}";

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P3: When a test address contains JSON-significant characters, jsonRequest creates malformed JSON instead of encoding the address as a JSON string. Escape the value with the project's JSON serializer (or otherwise quote it correctly) so this shared contract helper exercises the endpoint with the intended address.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At framework/src/test/java/org/tron/core/services/http/AddressQueryServletTestBase.java, line 71:

<comment>When a test address contains JSON-significant characters, `jsonRequest` creates malformed JSON instead of encoding the address as a JSON string. Escape the value with the project's JSON serializer (or otherwise quote it correctly) so this shared contract helper exercises the endpoint with the intended address.</comment>

<file context>
@@ -0,0 +1,196 @@
+
+  protected static MockHttpServletRequest jsonRequest(String address, String contentType) {
+    MockHttpServletRequest request = postRequest(contentType);
+    String json = address == null ? "{}" : "{\"address\":\"" + address + "\"}";
+    request.setContent(json.getBytes(UTF_8));
+    return request;
</file context>

getContract and getContractInfo answer null for an address that is not well
formed, before the account store is read.
@0xbigapple
0xbigapple force-pushed the fix/api-input-hardening branch 3 times, most recently from 7c48369 to 2c3bb5c Compare August 25, 2026 12:28
A string or number longer than 64 characters is rejected before BigDecimal
sees it, and Permission_id must convert identically through both coercion
paths, so a value one path silently reinterprets no longer gets through.
getAddress reports every way a request can fail to name an address as one
fixed message, and the reward and brokerage servlets write it, so the
fullnode, solidity and PBFT endpoints no longer differ on a given malformed
request. An absent address is one of those ways: it used to be answered with
the service default. The body is bounded while it is read, since this is the
only body-reading path that does not go through PostParams.
Mint takes exactly one receive description; transfer takes one or two of
each and, without an ask, a matching spend authority signature count. The
counts are checked before the merge loops, because ByteUtil.merge copies the
accumulated buffer on every iteration and checking afterwards would make an
unbounded list quadratic.
… failure

An amount string past 80 characters and a trigger input the builder rejects
are answered as a ContractValidateException naming the argument, instead of
escaping the wallet as an IllegalArgumentException.
@0xbigapple
0xbigapple force-pushed the fix/api-input-hardening branch from 2c3bb5c to 248cbe0 Compare August 25, 2026 13:56
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