fix(blockaid): stop dropping the site scan verdict on sign transaction - #2948
Open
aristidesstaffieri wants to merge 2 commits into
Open
fix(blockaid): stop dropping the site scan verdict on sign transaction#2948aristidesstaffieri wants to merge 2 commits into
aristidesstaffieri wants to merge 2 commits into
Conversation
…ach other
The site scan runs unawaited while fetchData keeps awaiting changeTrust icon
lookups. Both write to the same reducer slot, and helpers/request.ts
full-replaces `data`, so the later dispatch owned the whole payload and dropped
the other writer's field.
The final dispatch read `firstRenderPayload.siteScanData`, which is `undefined`
at construction and never mutated — the scan callback returns a new object
rather than writing back — so it always wrote `undefined`. When the scan landed
first, a real is_malicious verdict was replaced by `undefined`, which
getSiteSecurityStates reads as "scan in flight": no banner, and btnIsDestructive
loses isSiteMalicious. For an already-allow-listed dApp the site banner is the
only site-level scam signal, so the warning was suppressed entirely.
The reverse ordering lost the icons. The existing guard meant to prevent that
inspected firstRenderPayload.icons, hardcoded {} and never mutated, so its
condition never held.
Use refs as the shared source of truth for both contested fields, reset per
fetchData. Reading reducer state here would not work — fetchData closes over a
stale render's copy.
Contributor
|
PR Preview build is ready: https://github.com/stellar/freighter/releases/tag/untagged-81f1f3e38ddd4e970ee3 (SDF collaborators only — install instructions in the release description) |
aristidesstaffieri
marked this pull request as ready for review
August 10, 2026 18:06
Contributor
There was a problem hiding this comment.
Pull request overview
Fixes race conditions that could discard Blockaid site verdicts or transaction asset icons.
Changes:
- Shares scan verdict and icon state through refs.
- Resets shared state for each fetch.
- Adds tests for both async resolution orders.
Reviewed changes
Copilot reviewed 2 out of 2 changed files in this pull request and generated no comments.
| File | Description |
|---|---|
useGetSignTxData.tsx |
Preserves scan verdicts and icons across async dispatches. |
useGetSignTxData.test.tsx |
Tests both race-condition orderings. |
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
On the sign-transaction approval screen, a Blockaid
is_maliciousverdict for the requesting dApp could be discarded before it reached the UI: no malicious-site banner rendered, and Confirm was not marked destructive.useGetSignTxDatastarts the site scan unawaited and then dispatches a final payload assembled from a stale local object, while the reducer full-replacesdata— so whichever write landed last owned the entire payload and dropped the other writer's field. In the opposite resolution order the same defect silently blanked the fetched changeTrust asset icons.Both contested fields now live in refs shared by the two writers, so neither ordering loses data.
Root cause
firstRenderPayload.siteScanDataisundefinedat construction and is never mutated, because the scan callback returns a new object rather than writing back into it. The final dispatch read that field, so it wroteundefinedunconditionally; when the scan resolved first, its real verdict was overwritten.getSiteSecurityStatestreatsundefinedas "scan in flight" and returns all-clear, which suppresses both the banner and the destructive Confirm styling rather than degrading to "unable to scan".The reverse ordering lost the icons. The guard intended to prevent that inspected
firstRenderPayload.icons, which is hardcoded{}and never mutated, so its condition never held.Impact is bounded to dApps the user has already allow-listed.
renderBanneris priority-ordered and shows the domain-not-allowed banner first, so for every other origin the Blockaid site banner was already not the visible signal.What's in this PR
useGetSignTxData.tsx—siteScanDataRefandiconsRefas the shared source of truth between the unawaited scan callback and the finalfetchDatadispatch, reset perfetchData.useGetSignTxData.tsx— removes the previous icon-preservation guard, which could never fire.__tests__/useGetSignTxData.test.tsx— two tests pinning both resolution orderings, driven by hand-resolved deferreds.useAsyncSiteScan,useScanSite, the reducer andgetSiteSecurityStatesstay real; only the two network leaves are stubbed.Test plan
yarn jest --ci --selectProjects jsdom --runTestsByPath extension/src/popup/views/SignTransaction/hooks/__tests__/useGetSignTxData.test.tsx— 6/6yarn jest --ci --selectProjects jsdom --runTestsByPath extension/src/popup/views/__tests__/SignTransaction.test.tsx— 12/12yarn jest --ci --selectProjects jsdom -t "Blockaid"— 77 passed, no test failuresyarn tsc --noEmitclean; eslint clean on the changed sourcechangeTrusttransaction for an issuer absent from the icon cache from an allow-listed dApp. The banner must stay visible for the life of the popup and the asset icon must render — the two failure modes are mutually exclusive onmaster, so both holding at once confirms the fix.Followups
isSubmitDisabledinSignTransaction/index.tsxdoes not consider scan state, so a fast click can approve before the banner renders. This PR guarantees the verdict is not lost, not that it arrives before the user can act.data, so any future async enrichment added to this hook must extend the same ref bookkeeping or it will reintroduce the clobber. Awaiting the scan promise before the final dispatch — concurrent with the icon fetches, so no added latency in the slow path — would eliminate the class of bug. Deliberately out of scope here.