Repository navigation
fix(react-native-ui): accept offramp wallet requests only from the main frame (OFFRAMP-731) - #2293
Open
AngelPaella wants to merge 4 commits into
Conversation
…in frame (OFFRAMP-731)
…navigation and stamp only the own session (OFFRAMP-731)
…rchParams and warn when a load is not stamped (OFFRAMP-731)
🦋 Changeset detectedLatest commit: 5405be7 The changes in this PR will be included in the next version bump. This PR includes changesets to release 2 packages
Not sure what this means? Click here to learn what changesets are. Click here if you're a maintainer who wants to add another changeset to this PR |
…load start (OFFRAMP-731)
AngelPaella
marked this pull request as ready for review
November 6, 2026 18:40
Contributor
[Medium impact] The PR appears safe to merge within its stated interim scope. Reviews (1) · Last reviewed commit: "fix(react-native-ui): reset offramp wall..." · Reviewed by Greptile |
This branch has not been deployed
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
Linear: https://linear.app/crossmint/issue/OFFRAMP-731 (interim fix; the issue stays open)
On Android, react-native-webview gives
window.ReactNativeWebView.postMessageto every frame, and the message'surlis either the sender's origin (WebMessageListener) or the main page's URL (legacyaddJavascriptInterface). SoisOfframpPagecan't tell which frame posted. The offramp page now hosts a cross-origin frame (Persona's capture widget during KYC), and on the legacy path its messages pass the check. A frame could then sendcrypto:send-transactionorcrypto:sign-messageand have the host's payer sign.This PR does what Flutter already does (Crossmint/crossmint-flutter-sdk#117), with no change to the page:
injectJavaScript(main frame only on both platforms). It stamps every message with a fresh random nonce,__mainFrameNonce(16 bytes fromreact-native-get-random-values).crypto:send-transactionandcrypto:sign-messagewithout the current nonce are dropped with aconsole.warnand no reply, since a reply would reach the main frame, not the sender. Other events pass as before.apiKeyandcredentials, so a frame can't navigate the top frame to another session and get it stamped. Any other load clears the nonce and logs a warning. The two params are read withoutURL.searchParams, which older supported React Native versions don't implement (sameSession.ts).onLoadStart. On Android that event comes from same-documentpushState/replaceState, which the page's router does on hydration, with noonLoadafter it. OnlyonLoadrotates the nonce.offramp:ready(each new document sends it once), not ononLoadStart, as feat(offramp): payer.signMessage for in-iframe wallet ownership proof (OFFRAMP-664) #2266 already does on web. Before, areplaceStatewhile a signature was pending dropped the wallet's reply, and the page waited 120 s for a transaction or forever for a message signature. This also fixescrypto:send-transactionas it is on main.Stacked on #2266 (OFFRAMP-664), which adds
crypto:sign-message.What it leaves open, for OFFRAMP-731's contract-level fix:
offramp:completed(other events aren't checked).onLoad, or after a failed injection, are dropped, as in Flutter. Requests are user-initiated, so they normally come after the load.Test plan
packages/client/ui/react-native,crossmint-embedded-offramp.test.tsx: wallet requests with the current nonce are answered; without it, with a wrong, null or missing one, before any load, and with the previous load's nonce are dropped with no reply; the nonce survivesonLoadStart; non-wallet events pass without it; another session (othercredentialsorapiKey) and a non-offramp page get no nonce and a warning; the session check works withURLstubbed to throw and handles+/%20, param order, fragments, extra or missing params and malformed escapes; the injected script stamps, keeps the newer nonce on a second stamp, forwards unparsable messages, and posts once; pending send and sign replies still arrive afteronLoadStart, and afterofframp:readyan old reply is dropped while a new request works. 8 files, 115 tests passed.onLoadclear, the spread order, the+decoding or the fragment split, moving the post into the try, re-adding anonLoadStartnonce clear or pending reset, or removing theofframp:readyreset each fails a named test.tsc --noEmitshows no errors in the touched files; biome shows only test-file warnings.apps/wallets/expowith react-native-webview 14.0.1 on an iOS 26.5 simulator and an Android 16 emulator (WebView 133, WebMessageListener bridge). iOS: the ownership proof (crypto:sign-message) and the deposit (crypto:send-transaction) from the offramp page reached the payer; the deposit was declined on purpose and the page recovered. Android: the same for the deposit (the wallet was already verified). A same-origin https frame's own bridge request was dropped by the gate on Android; iOS frames have no bridge. A frame calling the main page's bridge was accepted, the same-origin residual above. Loading a frame did not reset the nonce. Not covered: the legacyaddJavascriptInterfacebridge, which needs an old Android WebView.Package updates
.changeset/: patch for@crossmint/client-sdk-react-native-ui.