Skip to content

Conversation

@smilingkylan
Copy link
Contributor

@smilingkylan smilingkylan commented Nov 24, 2025

Related issues

Fixes: #23222

  • issue where URL canonicalization was not properly sorting due to the way that .sort works on React Native.
  • add case for sig_params being empty, so that we can still sign links without any parameters that need to be signed
  • removed edge case unit test since it added very little value (no one should be using spaces in a signed URL)

Description

Reason for change:
Deep link signature verification was failing for marketing links (e.g., https://link.metamask.io/perps?sig_params=...&utm_...). The Mobile app's canonicalize function had diverged from the Extension's implementation, causing signature mismatches.

The root cause: React Native's URLSearchParams.sort() is not implemented — it throws an error. This meant parameters weren't being sorted correctly, so the canonical URL didn't match what was signed.

Improvement/solution:
Updated the canonicalize function to:

  1. Use custom array sorting instead of URLSearchParams.sort() (which is broken in React Native)
  2. Handle empty sig_params explicitly — when sig_params='', only include sig_params itself in the canonical URL (allows appending UTMs after signing)
  3. Use getAll()/append() instead of get()/set() to preserve multiple values for the same parameter (matches Extension behavior)
  4. Use encodeURIComponent() for consistent URL encoding

These changes align Mobile with the Extension's canonicalization logic, ensuring signed deep links verify correctly.

Changelog

CHANGELOG entry: Fixed deep link signature verification failing for marketing links with UTM parameters

Manual testing steps

Feature: Deep Link Signature Verification

  Scenario: user opens a signed deep link with sig_params and appended UTMs
    Given a signed deep link with sig_params listing specific parameters
    And UTM parameters are appended after signing

    When user opens the deep link in the app
    Then the signature verifies successfully
    And the user is not shown the unsigned link warning modal

  Scenario: user opens a signed deep link with empty sig_params
    Given a signed deep link with sig_params= (empty)
    And UTM parameters are appended after signing

    When user opens the deep link in the app
    Then the signature verifies successfully
    And only sig_params is used for verification (UTMs ignored)

  Scenario: user opens a signed deep link without sig_params (legacy)
    Given a signed deep link without any sig_params parameter

    When user opens the deep link in the app
    Then all parameters (except sig) are included in verification
    And the signature verifies successfully (backward compatibility)

Screenshots/Recordings

Before

Signed marketing links showed the "unsigned link" warning modal due to signature verification failure.

After

Signed marketing links verify correctly and open without the warning modal.

Pre-merge author checklist

Pre-merge reviewer checklist

  • I've manually tested the PR (e.g. pull and build branch, run the app, test code being changed).
  • I confirm that this PR addresses all acceptance criteria described in the ticket it closes and includes the necessary testing evidence such as recordings and or screenshots.

Labels to add: team-mobile-platform

@github-actions
Copy link
Contributor

CLA Signature Action: All authors have signed the CLA. You may need to manually re-run the blocking PR check if it doesn't pass in a few minutes.

@smilingkylan smilingkylan force-pushed the kylan/chore/fix-sign-verify branch from e3b5d81 to 10c9026 Compare November 25, 2025 16:26
@sethkfman sethkfman changed the title chore: fix signature verification sorting issue chore: fix signature verification sorting issue cp-7.56.0 Nov 25, 2025
Copy link
Contributor

@Cal-L Cal-L left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Left a comment

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Add unit test to cover cases that this intends to fix

@joaoloureirop joaoloureirop changed the title chore: fix signature verification sorting issue cp-7.56.0 chore: fix signature verification sorting issue cp-7.60.0 Nov 25, 2025
@codecov-commenter
Copy link

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 78.59%. Comparing base (861b876) to head (a1382d1).
⚠️ Report is 7 commits behind head on main.

Additional details and impacted files
@@            Coverage Diff             @@
##             main   #23220      +/-   ##
==========================================
+ Coverage   78.56%   78.59%   +0.02%     
==========================================
  Files        3947     3947              
  Lines      102433   102423      -10     
  Branches    20311    20303       -8     
==========================================
+ Hits        80481    80497      +16     
+ Misses      16407    16385      -22     
+ Partials     5545     5541       -4     

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@github-actions github-actions bot added size-M and removed size-S labels Nov 25, 2025
MarioAslau
MarioAslau previously approved these changes Nov 25, 2025
Copy link
Contributor

@MarioAslau MarioAslau left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM

@smilingkylan smilingkylan force-pushed the kylan/chore/fix-sign-verify branch from 056ba77 to bbfc297 Compare November 25, 2025 22:08
@github-actions github-actions bot added size-M and removed size-S labels Nov 25, 2025
@github-actions
Copy link
Contributor

🔍 Smart E2E Test Selection

  • Selected E2E tags: SmokeCore, SmokePerps, SmokeRewards, SmokePredictions, SmokeRamps, SmokeSwaps, SmokeCard
  • Risk Level: high
  • AI Confidence: 85%
click to see 🤖 AI reasoning details

The changes are in a critical deeplink signature verification utility (verifySignature.ts) located in app/core/DeeplinkManager/. This is a security-sensitive component that validates signed URLs for universal/deeplinks.

What Changed:
The canonicalize() function underwent significant refactoring to fix URL parameter sorting and handling:

  1. Parameter sorting logic changed: Now manually sorts params array instead of using URLSearchParams.sort()
  2. Empty sig_params handling: New special case for when sig_params is explicitly empty string
  3. Manual URL encoding: Now explicitly uses encodeURIComponent for both keys and values
  4. Bug fix for parameter ordering: Added test confirming fix for marketing link breakage due to alphabetical sorting (sig_params vs utm_*)

Why This is High Risk:

  1. Security Component: Signature verification protects against deeplink spoofing and unauthorized access
  2. Core Functionality: Located in app/core/ and used by DeeplinkManager, which routes to multiple features
  3. Wide Impact: The verifyDeeplinkSignature function is used by handleUniversalLink.ts which routes to:
    • Perps (PERPS, PERPS_MARKETS, PERPS_ASSET)
    • Rewards (REWARDS)
    • Predictions/Predict (PREDICT)
    • Ramps (BUY, SELL, BUY_CRYPTO, SELL_CRYPTO, DEPOSIT)
    • Swaps (SWAP)
    • Card (ENABLE_CARD_BUTTON)
    • Other core features (HOME, SEND, CREATE_ACCOUNT, ONBOARDING, WC, DAPP)
  4. URL Canonicalization Change: Any bug in URL canonicalization could cause valid signatures to fail or invalid ones to pass
  5. Regression Risk: Changes to cryptographic verification logic have high potential for regression

Why These Tags:

  • SmokeCore: CRITICAL - Core deeplink infrastructure affects app navigation and state management
  • SmokePerps: Deeplinks route to perps feature (handlePerpsUrl)
  • SmokeRewards: Deeplinks route to rewards (handleRewardsUrl)
  • SmokePredictions: Deeplinks route to predictions/predict (handlePredictUrl)
  • SmokeRamps: Deeplinks route to buy/sell ramps (handleRampUrl)
  • SmokeSwaps: Deeplinks route to swap feature (handleSwapUrl)
  • SmokeCard: Deeplinks route to card button enablement (handleEnableCardButton)

The changes are well-tested (comprehensive test updates included), but the security-critical nature and wide impact across multiple features warrant thorough E2E validation of all deeplink-dependent flows.

View GitHub Actions results

@sonarqubecloud
Copy link

Copy link
Contributor

@Cal-L Cal-L left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM

@Cal-L Cal-L added this pull request to the merge queue Nov 26, 2025
Merged via the queue into main with commit 65ae4f9 Nov 26, 2025
173 checks passed
@Cal-L Cal-L deleted the kylan/chore/fix-sign-verify branch November 26, 2025 00:51
@github-actions github-actions bot locked and limited conversation to collaborators Nov 26, 2025
@metamaskbot metamaskbot added the release-7.61.0 Issue or pull request that will be included in release 7.61.0 label Nov 26, 2025
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

release-7.61.0 Issue or pull request that will be included in release 7.61.0 size-M team-mobile-platform Mobile Platform team

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Utm parameters breaking deeplink signature

6 participants