harden: verify the signed transaction against what the popup displayed (closes #216)
All checks were successful
check / check (push) Successful in 32s
All checks were successful
check / check (push) Successful in 32s
The signed artifact was compared with the dApp's request object, so every field the dApp left out — normally the nonce, the gas limit and every fee field, because populateTransaction() filled them in the popup — was checked by nothing but the absolute ceilings. A bare transfer at the fee ceiling hands the validator 2.1 ETH. The ceilings were never the defect: the thing being verified was not the thing the user approved. The transaction is now populated in the background, before the approval window opens, and that populated object is what is displayed, what the popup signs, and what the artifact is verified against. Every consequential field is compared exactly. - src/shared/approvalTx.js populates the request through a VoidSigner over the configured RPC and serializes the result to the fields its type serializes, as hex quantities that survive the JSON messaging boundary. Fields the wallet does not act on are dropped before ethers sees the page's object. - Population failure raises no approval and opens no window: the error goes back to the requesting page, bounded by a 20-second timeout. A half-initialised approval record would be exactly the state the settle interlock exists to keep out of that record, and the same estimate previously failed after the user had typed their password. - verifySignedTx compares the artifact field by field over SERIALIZED_FIELDS[type], plus the type itself. A quantity the approval does not fix is a refusal rather than a skipped comparison. The ceilings stay as a documented backstop and now also apply at population, where they bound what an RPC node can talk the wallet into displaying. - The approval pins the address it was raised for. Verification uses that address, not getActiveAddress(), and an address switch between approval and signing refuses rather than signing from an account the screen never named — including a switch during population, and on the message-signing path. A request naming an address that is not the active one is refused outright. - The approval screen shows the network, gas limit, fee per gas, maximum fee and nonce it now vouches for, and the popup signs the object it was given with no provider and no population of its own. The settle chokepoint is untouched: one delete of pendingApprovals and one approval.resolve(), both inside settleApproval(), the claim taken synchronously before the first await, and a refused settle still leaving the approval window standing.
This commit is contained in:
16
README.md
16
README.md
@@ -1041,7 +1041,12 @@ on ConfirmTx, DeleteWallet, ApproveTx and ApproveSign.
|
||||
- **When**: A connected website requests a transaction via
|
||||
`eth_sendTransaction`. Always opened in a separate popup window by the
|
||||
background script (`windows.create()`), because the request is triggered
|
||||
programmatically rather than by a user gesture.
|
||||
programmatically rather than by a user gesture. The background populates the
|
||||
transaction (nonce, gas limit, fees, chain id) against the RPC node _before_
|
||||
opening the window, so the screen shows a complete transaction and the signed
|
||||
artifact can be compared with it field for field. A request that cannot be
|
||||
populated — unreachable node, reverting gas estimate — opens no window and is
|
||||
failed back to the site.
|
||||
- **Elements**:
|
||||
- "Transaction Request" heading
|
||||
- Phishing warning banner (shown when the hostname is on the phishing
|
||||
@@ -1053,13 +1058,16 @@ on ConfirmTx, DeleteWallet, ApproveTx and ApproveSign.
|
||||
- Contract: color dot + full address + etherscan link (or "contract
|
||||
creation"), token symbol label if known
|
||||
- Value: amount in ETH (4 decimal places, USD in parentheses)
|
||||
- Network fee (max): gas limit × fee per gas in ETH (4 decimal places, USD
|
||||
in parentheses), with the gas limit and the fee per gas in gwei below it
|
||||
- Network and nonce
|
||||
- Raw data: full calldata displayed inline (shown if present)
|
||||
- Password input and an error line
|
||||
- "Confirm" / "Reject" buttons
|
||||
- **Transitions**:
|
||||
- "Confirm" (correct password) → decrypts and signs in the popup, hands the
|
||||
signed transaction to the background to broadcast, then → **WaitTx** in
|
||||
the same popup window
|
||||
- "Confirm" (correct password) → decrypts and signs the transaction it was
|
||||
shown, exactly as shown, hands the signed transaction to the background to
|
||||
broadcast, then → **WaitTx** in the same popup window
|
||||
- "Confirm" (wrong password) → error line, no screen change
|
||||
- "Reject" → closes popup (returns rejection to background)
|
||||
- Popup window closed without answering → the request is rejected with
|
||||
|
||||
Reference in New Issue
Block a user