Is Crypto.com | Onchain Extension safe?
Crypto.com Onchain is high risk. We observed Crypto.com Onchain Extension POST Segment.io analytics on wallet init: write key, identify event, userId, and traits for Chrome/OS, extension version, wallet mode. It reads/creates a persistent device UUID resent later.…
AI-generated. Findings may contain errors. Those marked Verified have been manually reviewed.
Publishers can request a review.
Findings
Wallet unlock sends device UUID and wallet traits to Segment.io
We observed Crypto.com Onchain Extension POST Segment.io analytics on wallet init: write key, identify event, userId, and traits for Chrome/OS, extension version, wallet mode.
It reads/creates a persistent device UUID resent later.
- Severity
- High unwanted
- Type
- Unexpected
- CWE
- CWE-359
- Source
- Dynamic sandbox
You initialize, load, unlock, or set up the wallet extension.
The extension sends a Segment.io identify event tied to a persistent device UUID.
The event includes browser details and wallet-state traits such as wallet mode, default-wallet preference, and wallet counts.
Observed traffic showed a Segment identify batch with writeKey 2XTzCG6eEL02olczWanmJEqlBzYdus40, userId 8a1708cb-92e1-41f3-bd78-3bb9fef53334, Chrome 148.0.0, Linux, extension version 3.10.2, wallet mode initialize, and zero recovery phrase, private key, and ledger wallets.
- Device UUID8a1708cb-92e1-41f3-bd78-3bb9fef53334
A persistent identifier for this browser installation. Segment can use it to link later wallet telemetry back to the same installation.
- Browser and operating systemchrome 148.0.0 on Linux
Identifies the browser family, browser version, and operating system used when the wallet telemetry was sent.
- Extension version3.10.2
Tells Segment which build of the wallet extension produced the event.
- Wallet modeinitialize
Describes the wallet state or mode at the time the identify event was sent.
- Wallet countsrecoveryPhraseCount=0; privateKeyCount=0; ledgerCount=0
Reports how many recovery-phrase wallets, private-key wallets, and hardware wallets exist in the extension session.
- Default-wallet preferenceDefault Wallet: true when app_walletExtensionPreference_default is cdc
Shows whether the Crypto.com wallet is selected as the default wallet preference in local extension settings.
Source path that builds the Segment identify event
DeviceService.getDeviceUUID() stores a persistent UUID
assets/SuiNetworkSwapActivity-Dxad8qXW.jsasync getDeviceUUID() { let { uuid: eo } = await DeviceModel.devices.get({ id: 1 }) || {}; return eo || (eo = await this.generateDeviceUUID(), await this.setDeviceUUID(eo), await SegmentService.identify(!0)), eo}WalletService.getWalletCounts() counts wallet types
assets/SuiNetworkSwapActivity-Dxad8qXW.jsasync getWalletCounts() { const { wallets: eo } = await this.getWalletSession(); return { recoveryPhraseCount: eo.filter(no => no.walletType == "mnemonic").length, privateKeyCount: eo.filter(no => no.walletType == "privateKey").length, ledgerCount: eo.filter(no => no.walletType == "hardware").length }}SegmentService initializes Segment and sends identify traits
assets/SuiNetworkSwapActivity-Dxad8qXW.jsinitAnalyticsIf() { analytics || (analytics = new Analytics({ writeKey: "2XTzCG6eEL02olczWanmJEqlBzYdus40" }))},getDeviceInfo() { const { userAgentData: eo, language: no } = globalThis.navigator || {}; return { operatingSystem: eo?.platform ?? "", platform: "extension", locale: no, extensionVersion: "3.10.2" }},getBrowserInfo() { const eo = detect(); return { browser: eo?.name ?? "", browserVersion: eo?.version ?? "" }},async identify(eo) { const no = await this.readLocalStorage(SEGMENT_IDENTITY_LAST_UPDATED_KEY), oo = new Date; if (eo || no !== void 0 && Math.floor((oo.getTime() - new Date(no).getTime()) / (1e3 * 3600 * 24)) >= 1) { this.initAnalyticsIf(); const { currentWalletModelType: co } = await WalletService.getWalletConfig(), lo = await DeviceService.getDeviceUUID(), po = this.getDeviceInfo(), fo = this.getBrowserInfo(), yo = await WalletService.getWalletCounts(), { app_walletExtensionPreference_default: ka } = await chrome.storage.local.get("app_walletExtensionPreference_default"), Ba = Object.assign({ created: await this.getIdentityCreatedTime(), lastUpdated: oo.toISOString(), mode: co, "Default Wallet": ka === "cdc" }, po, fo, yo); return chrome.storage.local.set({ [SEGMENT_IDENTITY_LAST_UPDATED_KEY]: oo.toISOString() }), analytics.identify({ userId: lo, traits: Ba }) }}Segment Publisher sends the batch to api.segment.io/v1/batch
assets/SuiNetworkSwapActivity-Dxad8qXW.jsconstructor({ host: no, path: oo, maxRetries: so, flushAt: co, flushInterval: lo, writeKey: po, httpRequestTimeout: fo, httpClient: yo, disable: ka, oauthSettings: Ba}, $a) { this._emitter = $a, this._maxRetries = so, this._flushAt = Math.max(co, 1), this._flushInterval = lo, this._url = tryCreateFormattedUrl(no ?? "https://api.segment.io", oo ?? "/v1/batch"), this._httpRequestTimeout = fo ?? 1e4, this._disable = !!ka, this._httpClient = yo, this._writeKey = po, Ba && (this._tokenManager = new TokenManager({ ...Ba, httpClient: Ba.httpClient ?? yo, maxRetries: Ba.maxRetries ?? so }))}- api.segment.io
Segment.io batch analytics endpoint. It receives the identify event containing the persistent device UUID and wallet-state traits.
Wallet unlock reports wallet metadata to Crypto.com
Unlocking a non-mobile, non-hardware wallet POSTs to Crypto.com's device endpoint: device UUID, wallet UUID/name, address list as UTF-8 bytes, a signature over that list, wallet creation type.
Testing didn't capture this POST body.
- Severity
- Medium unwanted
- Type
- Unexpected
- CWE
- CWE-359
- Source
- Dynamic sandbox
You unlock the wallet in the extension.
The extension builds and sends a device report containing wallet identifiers, wallet addresses, and the wallet creation type.
- Device identifiera2d4f0d1-7a20-4c3e-8b59-80f4f1f9a0d2 (illustrative)
Ties the wallet report to the browser profile running your extension.
- Wallet identifierwallet-8c5d2f30-44ad-4f62-8e9a-8a6be534f1a1 (illustrative)
Ties the report to a specific wallet record inside the extension.
- Wallet nameMain Wallet (illustrative)
Shows the label you assigned to the wallet inside the extension.
- Wallet address bytesUTF-8 bytes for [{"address":"0x742d35Cc6634C0532925a3b844Bc454e4438f44e","network_id":"eth"}] (illustrative)
Carries the wallet address list after it has been converted into byte values.
- Address-list signature0x6b4ed0bcafc89e74f83277ed7b357b8a6e7b4e1c362948ea68f31c6b7832d8690a6c5be15ac9deee4b4db405f5fbdb02f96a1ad3e93ea9ad76b873d29ab6e88e1b (illustrative)
Provides a cryptographic proof tied to the address list in the report.
- Creation typeimported (illustrative)
Shows whether the wallet was created or imported in the extension.
Unlock handler and device-report POST path
Unlock handler calls reportDeviceInfo
assets/popup.html-C5udeqnP.jsrequestWalletUnlock = async t1 => { const { password: e1 } = t1; await WalletService.unlockCurrentWalletByPassword(e1), SegmentService.reportDeviceInfo(), await DappService.updateAllDappSession() }Device report request
assets/SuiNetworkSwapActivity-Dxad8qXW.jsasync reportDeviceInfo() { try { const eo = await DeviceService.getDeviceUUID(), { wallet: no, accountIndex: oo, walletType: so } = await WalletService.getWalletSession(); if (so === "mobile" || no?.walletType === "hardware" || !no) return; const lo = (await NetworkService.ensureQueryNetworks()).map($a => { const Ma = no?.addresses[$a.addressType]?.address, qa = $a.cdcNetworkId; return { address: Ma, network_id: qa } }).filter($a => $a.network_id), po = JSON.stringify(lo), fo = toUtf8Bytes(po), ka = (await DefiCoreService.getAccounts({ accountIndex: oo, wallet: no, chainType: "eth", addressType: "eth", isTestnet: !1 })).privkey, Ba = await EthereumService.signRaw({ dataToSign: po, privatekey: ka }); return request$4("/extension/ncw-api/api/v4/device", { method: "POST", data: { device_uuid: eo, sign: Ba, wallet: { uuid: no?.walletId, name: no?.name, addresses: Array.from(fo.values()) }, creation_type: no?.creationType } }) } catch {} }- ex-wallet.crypto.com
Crypto.com extension backend receives the constructed device report.
Transaction broadcasts report wallet and dapp context
After a transaction broadcast, the extension sends event extension_send_transaction: chain details, tx hash, wallet address, source, dapp host, an ETH address.
Also copied to Crypto.com's analytics backend; testing missed this body.
- Severity
- Medium unwanted
- Type
- Unexpected
- CWE
- CWE-359
- Source
- Dynamic sandbox
You broadcast a blockchain transaction from the extension.
The extension sends an analytics event with the transaction hash, wallet address, chain details, source, and dapp host when present.
- Event nameextension_send_transaction
Identifies this as a transaction-sent event tied to your wallet activity.
- Transaction hash0x51f4d9fb8e0a97f14f53f05a5f5a95b8ef49f774c2a390c026b5d829de21f019 (illustrative)
Identifies the specific blockchain transaction you sent.
- Wallet address0x742d35Cc6634C0532925a3b844Bc454e4438f44e (illustrative)
Ties the transaction event to your wallet address on the selected chain.
- Chain and networkchainId: 1, chainType: evm, network: ethereum (illustrative)
Shows which blockchain network you used for the transaction.
- Connected dapp hostapp.uniswap.org (illustrative)
Shows which website was associated with the transaction when it came from a connected dapp.
- ETH address value0x9f8d2cC0b9123F9a9F1234567890aBCDef123456 (illustrative)
Adds another wallet address value to the same analytics event.
Transaction event construction and backend copy
Broadcast call site
assets/start_server.ts-CEBWpU-X.jsawait _l.trackingBroadcastTransaction({ hash: B.hash, network: P, origin: e.req.from, source: K }), e.pushResponse(B.hash)Backend copy of analytics event
assets/SuiNetworkSwapActivity-Dxad8qXW.jsasync reportTrackToBackend(eo) { return request$4("/extension/ncw-api/api/data/ext", { method: "POST", data: { ...eo, type: "track", timestamp: new Date().toISOString(), messageId: `node-${md5$3(JSON.stringify(eo))}-${Xa()}` } }) }Transaction tracking event
assets/SuiNetworkSwapActivity-Dxad8qXW.jsasync trackingBroadcastTransaction(eo) { try { const { network: no, origin: oo, source: so, hash: co, protocolTag: lo, fromTokenFiatPrice: po, toTokenFiatPrice: fo, feeTokenFiatPrice: yo, fiatCurrency: ka } = eo, Ba = await TransactionModel.transactions.where({ hash: eo.hash }).first(); if (!Ba) { BugsnagService.notify(Ba, { method: "not found transaction in DataTrackingService.trackingBroadcastTransaction", extraContext: { network: no, origin: oo, source: so, hash: co, protocolTag: lo, fromTokenFiatPrice: po, toTokenFiatPrice: fo, feeTokenFiatPrice: yo, fiatCurrency: ka } }); return } const $a = await WalletService.getWalletById(Ba.walletId); let Ma = "extension_native", qa = { txnDapp: "Crypto Wallet Extension", protocolTag: eo.protocolTag, fromTokenFiatPrice: eo.fromTokenFiatPrice, toTokenFiatPrice: eo.toTokenFiatPrice, feeTokenFiatPrice: eo.feeTokenFiatPrice, fiatCurrency: eo.fiatCurrency }; const za = await ConnectSessionService.getDappInfo(oo); za && (Ma = "extension_dapp", qa = { txnDapp: za.clientMeta.name ?? "", dappHost: za.origin?.replace(/^https?:\/\//, ""), protocolTag: eo.protocolTag, fromTokenFiatPrice: eo.fromTokenFiatPrice, toTokenFiatPrice: eo.toTokenFiatPrice, feeTokenFiatPrice: eo.feeTokenFiatPrice, fiatCurrency: eo.fiatCurrency, dappConnectType: "extension" }); let Ga = { hasMetaData: !1 }; if (ActivityUtils.isCosmosTransaction(Ba)) { const Ka = typeof Ba.cosmos.amount[0] != "string" ? Ba.cosmos.amount[0].denom : "uatom", Ja = typeof Ba.cosmos.amount[0] != "string" ? Ba.cosmos.amount[0].amount : Ba.cosmos.amount[0], ss = await CurrencyModel.currencies.where({ denom: Ka }).first(), ds = mathjs.divide(mathjs.bignumber(Ja), 10 ** (ss?.tokenDecimal ?? 6)).toString(); Ga = { hasMetaData: !0, metaData: { txAction: "send", fromTokenCoinId: ss?.coinId ?? "", fromTokenAddress: Ba.cosmos.msgs[0].value.fromAddress ?? "", fromTokenSymbol: ss?.symbol ?? "", fromTokenAmount: ds, fromTokenDecimals: ss?.tokenDecimal.toString() ?? "", toTokenCoinId: ss?.coinId ?? "", toTokenAddress: Ba.cosmos.msgs[0].value.toAddress ?? "", toTokenSymbol: ss?.symbol ?? "", toTokenAmount: ds, toTokenDecimals: ss?.tokenDecimal.toString() ?? "" } } } await SegmentService.track({ event: "extension_send_transaction", properties: { chainId: Ba.chainId, chainType: Ba.chainType, chainCode: no.chainCode, network: no.code, transactionId: Ba.hash, walletAddress: $a?.addresses[no.addressType].address, source: so, app: Ma, details: qa, ...Ga }, extra: { ethAddr: $a?.addresses.eth.address } }, { isReportToBackend: !0 }) } catch (no) { await BugsnagService.notify(no, { method: "DataTrackingService.trackingBroadcastTransaction error" }) } }- api.segment.io
Segment analytics receives the extension_send_transaction event through analytics.track().
- ex-wallet.crypto.com
Crypto.com extension backend receives a copy of the same track event.
+1 more finding not shown