Receiver pre-filled from URL query param (?address=)

Component: ghost-lite @ a420393, src/containers/Transactions.tsx:37-38,54; internal trigger in src/containers/AddressBook.tsx:127-130 (openTransfer → navigate('/transactions?address=...')).

Description: The Transactions page initializes the receiver state directly from the URL: const initialReceiver = queryParams.get("address") ?? "". Anyone can craft a link https://<app>/#/transactions?address=<attacker-SS58-address> and lure the victim into pasting an amount and pressing Transfer. This is a by-design feature for the address book’s “Send” button, but it accepts input from arbitrary external links without distinction.

Impact: Social-engineering amplifier. Existing verified mitigations: (1) the full address is shown in the input (not truncated), (2) receiverObject validates the checksum via ss58Decode and the prefix must match the chain’s ss58Format (Transactions.tsx:88-99): calldata is not built for invalid addresses, (3) the wallet signing popup shows the real decode. Exploitation needs a victim who carelessly ignores a clearly displayed address.

Reproduction: Static: open /#/transactions?address=<valid SS58> → the receiver field auto-fills with the URL’s address.

Severity: Low. Reason: behavior verified, but the exploit chain needs heavy social engineering against three layers of visibility/validation.

Recommendation: When address comes from the query param (not internal address-book navigation), show a confirmation banner (“Receiver filled from link, verify the address”), or consume and strip the param from the URL (navigate(..., {replace:true})).

Verification: VERIFIED.

Thank you for your report!

But this small routing feature is expected to be used alongside sending a sort of receive link. So for now it works as expected - at the same time, it does open up a social-engineering attack surface. What trade-offs would you recommend here?

My recommended trade-off, keeping the receive-link feature intact:

  1. Distinguish internal from external navigation. Today the address book’s Send button and an arbitrary external link both arrive as ?address= in the URL, so the app cannot tell them apart. Pass the address via router state for the internal Send flow, and treat a bare ?address= param (without router state) as external. External prefills get a dismissible banner: “Receiver filled from a link. Verify the address before sending.” Internal navigation shows no banner, so the normal UX is untouched.

  2. Consume and strip the param. After reading ?address= on mount, navigate with replace:true to remove it from the URL. The prefill still works, but the address no longer sits in a copy-pasteable, forwardable link afterwards.

  3. Keep what already works: full-address display, checksum validation, and the wallet decode popup. Those three are doing the real work today.

This keeps the one-click receive link working exactly as before for legitimate use, while an attacker-crafted link now announces itself instead of prefilling silently.