Is Crypto.com | Onchain Extension safe?

High risk

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.…

Crypto.comv3.10.4Chrome Web Store
77Risk

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
What actually happens
You did this

You initialize, load, unlock, or set up the wallet extension.

The extension did this

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.

Captured request
POSThttps://api.segment.io/v1/batch

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.

Fields in the Segment identify telemetry
  • Device UUID
    8a1708cb-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 system
    chrome 148.0.0 on Linux

    Identifies the browser family, browser version, and operating system used when the wallet telemetry was sent.

  • Extension version
    3.10.2

    Tells Segment which build of the wallet extension produced the event.

  • Wallet mode
    initialize

    Describes the wallet state or mode at the time the identify event was sent.

  • Wallet counts
    recoveryPhraseCount=0; privateKeyCount=0; ledgerCount=0

    Reports how many recovery-phrase wallets, private-key wallets, and hardware wallets exist in the extension session.

  • Default-wallet preference
    Default 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.

The code that does this

Source path that builds the Segment identify event

Readable version

DeviceService.getDeviceUUID() stores a persistent UUID

assets/SuiNetworkSwapActivity-Dxad8qXW.js
async 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.js
async 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.js
initAnalyticsIf() {  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.js
constructor({  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  }))}
External analytics recipient
    • 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
What actually happens
You did this

You unlock the wallet in the extension.

The extension did this

The extension builds and sends a device report containing wallet identifiers, wallet addresses, and the wallet creation type.

Fields constructed for the device report
  • Device identifier
    a2d4f0d1-7a20-4c3e-8b59-80f4f1f9a0d2 (illustrative)

    Ties the wallet report to the browser profile running your extension.

  • Wallet identifier
    wallet-8c5d2f30-44ad-4f62-8e9a-8a6be534f1a1 (illustrative)

    Ties the report to a specific wallet record inside the extension.

  • Wallet name
    Main Wallet (illustrative)

    Shows the label you assigned to the wallet inside the extension.

  • Wallet address bytes
    UTF-8 bytes for [{"address":"0x742d35Cc6634C0532925a3b844Bc454e4438f44e","network_id":"eth"}] (illustrative)

    Carries the wallet address list after it has been converted into byte values.

  • Address-list signature
    0x6b4ed0bcafc89e74f83277ed7b357b8a6e7b4e1c362948ea68f31c6b7832d8690a6c5be15ac9deee4b4db405f5fbdb02f96a1ad3e93ea9ad76b873d29ab6e88e1b (illustrative)

    Provides a cryptographic proof tied to the address list in the report.

  • Creation type
    imported (illustrative)

    Shows whether the wallet was created or imported in the extension.

Captured request
POSThttps://ex-wallet.crypto.com/extension/ncw-api/api/v4/device
The code that does this

Unlock handler and device-report POST path

Readable version

Unlock handler calls reportDeviceInfo

assets/popup.html-C5udeqnP.js
requestWalletUnlock = async t1 => {    const {      password: e1    } = t1;    await WalletService.unlockCurrentWalletByPassword(e1), SegmentService.reportDeviceInfo(), await DappService.updateAllDappSession()  }

Device report request

assets/SuiNetworkSwapActivity-Dxad8qXW.js
async 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 {}    }
Destination host
    • 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
What actually happens
You did this

You broadcast a blockchain transaction from the extension.

The extension did this

The extension sends an analytics event with the transaction hash, wallet address, chain details, source, and dapp host when present.

Fields in the transaction event
  • Event name
    extension_send_transaction

    Identifies this as a transaction-sent event tied to your wallet activity.

  • Transaction hash
    0x51f4d9fb8e0a97f14f53f05a5f5a95b8ef49f774c2a390c026b5d829de21f019 (illustrative)

    Identifies the specific blockchain transaction you sent.

  • Wallet address
    0x742d35Cc6634C0532925a3b844Bc454e4438f44e (illustrative)

    Ties the transaction event to your wallet address on the selected chain.

  • Chain and network
    chainId: 1, chainType: evm, network: ethereum (illustrative)

    Shows which blockchain network you used for the transaction.

  • Connected dapp host
    app.uniswap.org (illustrative)

    Shows which website was associated with the transaction when it came from a connected dapp.

  • ETH address value
    0x9f8d2cC0b9123F9a9F1234567890aBCDef123456 (illustrative)

    Adds another wallet address value to the same analytics event.

Captured request
POSThttps://ex-wallet.crypto.com/extension/ncw-api/api/data/ext
The code that does this

Transaction event construction and backend copy

Readable version

Broadcast call site

assets/start_server.ts-CEBWpU-X.js
await _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.js
async 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.js
async 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"        })      }    }
Destination hosts
    • 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

Updated 30 September 2026hifafgmccdpekplomjjkcfgodnhcellj