88914 w a high hide balances masks executable multisig amounts enabling deceptive utility if else approvals
Submitted on Aug 15th 2026 at 15:55:18 UTC by @zkbm for Audit Comp | Quantus
Report ID: #88914
Report Type: Websites & Apps
Report severity: High
Target: https://github.com/immunefi-team/audit-comp-quantus-apps/tree/audit-comp-ready/mobile-app
Impacts:
Direct theft of user funds
Malicious interactions with an already-connected wallet, such as:
Modifying transaction arguments or parameters
Substituting contract addresses
Submitting malicious transactions
Description
Brief/Intro
The mobile application's privacy setting for hiding account balances is also applied to amounts inside the decoded runtime call shown on a multisig authorization screen. Native-token AmountField values are -----, but the Approve button remains enabled.
Composite calls make this exploitable rather than merely inconvenient. For utility.if_else, the confirmation headline is derived only from the primary branch. A malicious cosigner can therefore propose a call whose primary branch appears to transfer 1 QUAN but is guaranteed to fail, while its fallback transfers a much larger amount to the same expected recipient. The screen headlines the proposal as 1 QUAN, masks the fallback amount, and permits the victim to approve it. Once the approval threshold is reached, the attacker can execute the proposal and the fallback transfer runs. The PoC proves that fallback transfers of 2 QUAN and 999,999.999999999999 QUAN have different SCALE-encoded call bytes but produce identical authorization text.
I think this could reasonably be scored as a High or Critical - since it does involve some level of user interaction, but to me this is a transaction authorization integrity flaw, not just a cosmetic UX issue. You can present materially different signed payloads with identical authorization text that conceals the amount that can execute and leaves approval enabled. Considering the ByBit hack root cause (manipulation of safe signing), I think it could be considered critical but didn't want to oversell the rating. Hide balances is a global, persistent privacy setting, so it's reasonable that a user would have this enabled while viewing another account and later enter an authorization flow with the fallback amounts concealed.
Vulnerability Details
mobile-app/lib/v2/components/decoded_call_view.dart renders a native-token AmountField through the general transaction display provider and retains only its primaryAmount:
mobile-app/lib/providers/currency_display_provider.dart makes that provider dependent on the global isBalanceHiddenProvider. When the privacy option is active, all amounts collapse to the same ----- string. The same provider also defaults to two token decimals and may select rounded fiat as the primary display, making it unsuitable for authorization even when privacy mode is off.
mobile-app/lib/v2/screens/multisig/multisig_action_confirm_sheet.dart displays the decoded call but only blocks approval while call bytes are loading or unavailable. It does not block approval when signed fields are redacted:
Finally, quantus_sdk/lib/src/chain/call_decoder.dart assigns utility.if_else the summary of its primary branch:
This combination creates a misleading 1 QUAN headline while the amount in the branch that will actually execute is hidden.
Attack scenario
Impact
An attacker who controls one multisig member can obtain another member's approval for materially different transfer arguments than the authorization UI communicates. Once the threshold is reached, this results in direct theft of the multisig's funds.
This maps to the in-scope Critical impacts Direct theft of user funds and Malicious interactions with an already-connected wallet, including modifying transaction arguments or parameters.
Recommended fix
Proof of Concept
Save the following as hidden_multisig_amount_poc_test.dart in the test directory (mobile-app/test/unit/hidden_multisig_amount_poc_test.dart). Run it from the mobile-app directory after your Flutter setup is done.
Expected result:
The test constructs and SCALE-encodes real batch_all and if_else runtime calls using the generated runtime types. It decodes those bytes with the production CallDecoder and renders the production MultisigActionConfirmSheet.
The test demonstrates the following:
The signed 999,999.999999999999 QUAN amount is replaced by -----.
Both amounts in the conditional call are hidden.
The Approve control has a non-null tap handler.
The authorization headline says 1 QUAN based only on the primary branch.
Scrolling to the fallback does not reveal its amount.
Changing the fallback from 2 QUAN to 999,999.999999999999 QUAN changes the signed SCALE bytes but produces identical rendered authorization text.
Attached is a screenshot from the victim's point of view. As you can see, this is extremely deceptive since the victim reasonably believes that they are authorizing the transfer of 1 QUAN. The executable fallback amount can't be verified by the victim.
Was this helpful?