Displayed transaction summary is not derived from the signed payload
Larkspur Wallet SDK · packages/sdk-core/src/sign/request.ts:L64–L118
buildSignRequest accepts a transaction and an optional display object. When display is present, its recipient, amount and memo are used for the human-readable summary sent to the wallet; the SignDoc that will actually be signed is serialised separately from the transaction argument.
Nothing checks that the two agree. A page that controls both arguments can show the user "Send 1 ATOM to cosmos1…abc" while the SignDoc contains a MsgSend of the full balance to a different address, or an authz MsgGrant.
Exploit sequence
- A compromised dApp page calls buildSignRequest with a MsgGrant giving the attacker authz over the user’s funds.
- It passes display = { recipient: <the expected address>, amount: "1 ATOM" }.
- The wallet shows the small transfer; the user approves.
- The signed MsgGrant lets the attacker execute transfers on the user’s behalf.
Recommendation
Remove the display override. Generate the summary by decoding the exact bytes of the SignDoc that will be signed, render unknown message types explicitly as unrecognised, and add differential tests that decode each request independently.
-export function buildSignRequest(tx: TxInput, display?: DisplayHints): SignRequest {- const signDoc = serializeSignDoc(tx)- const summary = display ? summaryFromHints(display) : summaryFromTx(tx)+export function buildSignRequest(tx: TxInput): SignRequest {+ const signDoc = serializeSignDoc(tx)+ // The summary is always decoded from the bytes that will be signed.+ const summary = summaryFromSignDoc(decodeSignDoc(signDoc.bytes)) return { signDoc, summary } }
