MetaMask / MetaMask/metamask-mobile
[Bug]: Security scans receive the WalletConnect channel id as request origin - false Malicious (permit_farming) verdicts on verified, allowlisted dapps
- Dominant language
- TypeScript
- Stars
- 3k
- Forks
- 1.7k
- Avg merge
- 1d 14h
- Merged PRs (30d)
- 669
Description
### Describe the bug
For dapp requests received over WalletConnect, the JSON-RPC request submitted to the Security Alerts API (Blockaid) for signature scanning carries the WalletConnect channel id as `origin` instead of any dapp identifier. Blockaid therefore cannot apply its domain allowlist or any origin-based signal, and legitimate signature requests from verified dapps are misclassified as malicious.
Concrete case: an EIP-2612 permit from our payment checkout at `https://subscribe.confirmo.com` - domain-verified via the WalletConnect Verify API (`verifyContext.verified.validation === 'VALID'` on the individual payload) and domain-allowlisted with Blockaid - is returned as `Malicious` / `permit_farming`, and users are shown the "third party known for scams" banner on a legitimate payment flow. The identical flow passes cleanly in the MetaMask Extension.
Root cause (verified on `release/8.7.0`): `BackgroundBridge.setupProviderEngineEip1193` sets `const origin = this.channelIdOrOrigin;` and pushes `createOriginMiddleware({ origin })` - for WalletConnect bridges `channelIdOrOrigin` resolves to the raw channel id, so every JSON-RPC request is stamped with it. `RPCMethodMiddleware` then calls `PPOMUtil.validateRequest(req)` with that request, and the channel id is submitted verbatim as `origin` to the Security Alerts API (confirmed from device state logs - see log output below; the `securityAlerts` state stores the exact submitted request body).
The `eth_sendTransaction` path on the same session does not have this bug - `WalletConnect2Session.handleSendTransaction` passes `getUnverifiedRequestOrigin()` (a URL) - which provides a clean control case: an approval with identical trust shape (unlimited allowance to an equally counterfactual spender), scanned seconds earlier with `origin: "https://subscribe.confirmo.com"`, returned `Benign`. The origin field is the differential.
### Expected behavior
The scan request should never carry the channel id as `origin`. It should carry either:
1. a verifiable dapp origin - `verifyContext.verified.origin` when the Verify API attestation resolves to `validation: 'VALID'` (this is per-payload, browser-attested, and registry-matched - not self-reported `metadata.url`; and `getUnverifiedRequestOrigin` in `wc-utils.ts` already prefers exactly this value since #29102), or
2. no `origin` at all, when no valid attestation exists.
We're aware #34570 (release-8.9.0) addresses the garbage-origin half by removing the origin from scans entirely for remote transports. Note that this fixes the wrong-input problem but not the misclassification: with no origin, Blockaid domain allowlists become permanently unreachable for all WalletConnect traffic (they are the mechanism Blockaid itself prescribes for legitimate dapps - including ours, whose spender contracts are counterfactual/undeployed at signature time and therefore always read as "untrusted EOA" to payload heuristics). A conditional - drop the origin unless `validation === 'VALID'`, in which case send `verifyContext.verified.origin` - would fix both halves, and also extends naturally to the confirmation display (ref. the end state described in #34570, MCWP-771).
### Screenshots/Recordings
All screenshots captured against the same checkout.
Metamask approve OK:
Metamask permit bug:
Reown reference wallet showing permit with valid origin domain attestation:
### Steps to reproduce
1. From a mobile browser, open a dapp whose domain is verified with the WalletConnect Verify API and allowlisted with Blockaid (reproducible against our production checkout - we can provide a test payment link on request).
2. Connect MetaMask Mobile via WalletConnect.
3. Trigger an `eth_signTypedData_v4` request (EIP-2612 permit).
4. Observe the malicious banner on the confirmation.
5. Settings -> download state logs. In `securityAlerts.alerts`, the entry for the request shows `req.origin` = the WalletConnect channel id (64-char hex) and `result_type: "Malicious"`, `reason: "permit_farming"`.
6. Control: on the same session, trigger an `eth_sendTransaction` (ERC-20 approve). Its `securityAlerts` entry shows `req.origin` = the dapp URL and `result_type: "Benign"`.
### Error messages or log output
```shell
Failing scan - permit, origin is the WC channel id (Sentry traceContext omitted):
"1787586054559454": {
"block": 92587353,
"result_type": "Malicious",
"reason": "permit_farming",
"description": "permit_farming to spender 0x9378c04328c271b6fcbd98f7c564bb77ade2eccb, classification: The spender 0x9378c04328c271b6fcbd98f7c564bb77ade2eccb is untrusted EOA; FV|Events: Approval(0x6c70383b60ec478884fef8505e053e4cba78c993,0x9378c04328c271b6fcbd98f7c564bb77ade2eccb,0xc2132d05d31c914a87c6611c10748aeb04b58e8f)",
"features": ["The spender 0x9378c04328c271b6fcbd98f7c564bb77ade2eccb is untrusted EOA"],
"source": "api",
"req": {
"id": 1787586054559454,
"topic": "004ff81dcc1357da219804a401683d16a62689bb2e0c807995b60a6960b0aa75",
"method": "eth_signTypedData_v4",
"params": ["0x6c70383B60Ec478884fef8505E053E4CBA78C993", "{\"domain\":{\"name\":\"USDT0\",\"version\":\"1\",\"chainId\":137,\"verifyingContract\":\"0xc2132d05d31c914a87c6611c10748aeb04b58e8f\"},\"message\":{\"owner\":\"0x6c70383b60ec478884fef8505e053e4cba78c993\",\"spender\":\"0x9378c04328c271b6fcbd98f7c564bb77ade2eccb\",\"value\":\"115792089237316195423570985008687907853269984665640564039457584007913129639935\",\"nonce\":\"0\",\"deadline\":\"1787589654\"},\"primaryType\":\"Permit\",\"types\":{\"EIP712Domain\":[{\"name\":\"name\",\"type\":\"string\"},{\"name\":\"version\",\"type\":\"string\"},{\"name\":\"chainId\",\"type\":\"uint256\"},{\"name\":\"verifyingContract\",\"type\":\"address\"}],\"Permit\":[{\"name\":\"owner\",\"type\":\"address\"},{\"name\":\"spender\",\"type\":\"address\"},{\"name\":\"value\",\"type\":\"uint256\"},{\"name\":\"nonce\",\"type\":\"uint256\"},{\"name\":\"deadline\",\"type\":\"uint256\"}]}}"],
"origin": "64c088a75e36b8d4ad1f40af3a1f0c0e54caf995d68f7fe05931545392386bd2",
"networkClientId": "polygon-mainnet"
},
"chainId": "0x89"
}
Passing control - approve on the same session ~16s earlier, this code path sends a URL origin:
"29508f50-9fd2-11f1-8e4a-13abb2a7d32e": {
"block": 25825997,
"result_type": "Benign",
"reason": "",
"description": "LB|Events: Approval(0x6c70383b60ec478884fef8505e053e4cba78c993,0x93c8a5f64f7bd7212a325592c997b918fdcb04ed,0xdac17f958d2ee523a2206206994597c13d831ec7)",
"features": [],
"source": "api",
"req": {
"id": 1787586038389900,
"jsonrpc": "2.0",
"method": "eth_sendTransaction",
"origin": "https://subscribe.confirmo.com",
"params": [{
"from": "0x6c70383B60Ec478884fef8505E053E4CBA78C993",
"to": "0xdAC17F958D2ee523a2206206994597C13D831ec7",
"data": "0x095ea7b300000000000000000000000093c8a5f64f7bd7212a325592c997b918fdcb04edffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"
}]
},
"chainId": "0x1"
}
Corresponding SignatureController state for the permit confirms the stamped origin (messageParams.origin = "64c088a75e36b8d4ad1f40af3a1f0c0e54caf995d68f7fe05931545392386bd2" while meta.url = "https://subscribe.confirmo.com").
```
### Where was this bug found?
Live version (from official store)
### Version
8.7.1
### Build number
6489
### Build type
None
### Device
Pixel 6 (Android 17); also reproduced on iPhone 15 (iOS 26.6)
### Operating system
Android
### Additional context
- Confirmed on both platforms: Pixel 6 (Android 17) and iPhone 15 (iOS 26.6), both on MetaMask 8.7.1 (build 6489). This is expected, as the affected code is shared React Native/JavaScript (`BackgroundBridge`, `RPCMethodMiddleware`, `ppom-util`), not platform-specific. State logs below were captured on Android.
- History of this origin: #29072 reported the channel id surfacing in the typed-sign UI; #31667 fixed the display by switching to `meta.url`, but the underlying request origin remained the channel id and continues to flow into the security scan.
- #30547 introduced the "External app" label for the same requests, so users currently see a scam-classified confirmation from an anonymous "External app" - indistinguishable from an actual phishing attempt - on legitimate, Verify-API-verified payment flows. This trains users to click through exactly the warning that should protect them.
- Heads-up on #34570 (release-8.9.0): it removes the origin from the `eth_sendTransaction` scan path as well, so the currently-passing control case above will regress, and Blockaid domain allowlisting becomes unreachable for all WalletConnect traffic in MetaMask Mobile.
- Confirmo is a regulated European crypto payment provider (Central Bank of Ireland-authorized MiCA CASP and Payment Institution). This misclassification affects the security-critical step of every mobile WalletConnect payment we process.
- Blockaid has directed us to domain allowlisting as the intended mechanism for our case (our per-payment spender contracts are counterfactual, so address-based allowlisting is not applicable - the dapp origin is the only stable trust signal our requests carry).
- Reported in parallel to Reown (who have forwarded it to your team) and to Blockaid. We can provide a reproducible test setup against our production checkout, additional verdict captures on specific versions, and are happy to test any fix build.
### Severity
_No response_
Contributor guide
Research direction
Trace BackgroundBridge.setupProviderEngineEip1193, createOriginMiddleware, RPCMethodMiddleware, PPOMUtil.validateRequest, and wc-utils.ts. Compare the WalletConnect signature-scan path with WalletConnect2Session.handleSendTransaction and the existing Verify API origin handling. Done means scans never submit a channel ID, use the verified origin only for VALID attestations, and omit origin otherwise.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- react-native, typescript
- Domain
- mobile-dev, security
- Issue type
- Bug
- Difficulty
- 4/5
- Estimated time
- 3-5 days
- Activity status
- Active
- Clarity
- Mostly clear
- Newbie friendliness
- 55/100