fix: queue intents until unlocked and bind send to source account (M10, L10)#582
fix: queue intents until unlocked and bind send to source account (M10, L10)#582n13 wants to merge 3 commits into
Conversation
…0, L10) M10: deep-link and notification intents were consumed by HomeScreen's listeners even while the lock overlay was up, pushing attacker-crafted screens underneath it. Intent handlers now require localAuthProvider.isAuthenticated; intents arriving while locked stay queued in their providers and are drained on unlock. L10: RegularSendStrategy re-resolved the source account at submit time, so an account switch between review and confirm signed from the new account. The strategy now captures the source account at flow start and uses it for fee estimation, balance validation and submission. Intents that navigate or switch the active account are also deferred while a send flow is on the stack, so a notification tap can't hijack an in-flight send.
🔍 Review — M10, L10 (intent gating & send-account binding)Verdict: 🟡 Approve with non-blocking comments The core fixes are correct and well-targeted: intent handlers now require What it does
Strengths
Findings
VerificationStated verification ( 🤖 AI-assisted review generated with Claude Code |
…ting-send-account
Summary
Addresses findings M10 and L10 of the 2026-07-22 mobile wallet security audit.
M10 — intents consumed beneath the lock overlay
Deep-link (
/pay,/account) and notification (transaction, multisig proposal) intents were acted on byHomeScreen's listeners while the lock overlay was up, so e.g. ahttps://www.quantus.com/pay?to=…&amount=…link pushed an attacker pre-filledInputAmountScreenwhile the app was locked — the first thing the user saw after unlock.home_screen.dartnow requirelocalAuthProvider.isAuthenticatedbefore consuming.StateProviders (queued) and are drained whenisAuthenticatedflips to true (new listener inHomeScreen.initState).activeAccountProvidernow drains all pending intents, not just transaction intents.L10 — source account re-resolved at submit time
RegularSendStrategywas account-less and re-resolved the signing account at submit time (SettingsService().getActiveRegularAccount()), so an account switch between review and confirm (e.g. via a proposal-notification tap) signed from the new account.RegularSendStrategynow captures the sourceAccountat flow start (matchingEncryptedSendStrategy/MultisigProposeStrategy) and uses it for fee estimation, balance validation and submission. Callers (home send button, payment intent, shared-address sheet) pass the account in; payment/shared-entry flows require an active regular account.effectiveMaxBalanceProviderFamily(accountId)binds spendable-balance validation to the captured account instead of the active account.sendFlowActiveProvidercounts send-flow root screens (SelectRecipientScreen/InputAmountScreen). While a send is in flight, intents that navigate or switch the active account (payment, shared-account, proposal) are deferred and drained when the flow leaves the stack — a notification tap can no longer hijack an in-flight send.Verification
dart analyzeinmobile-app/: no issues.flutter test test/unit: 222/222 passed.Notes