Is HAVAH Wallet safe?

Medium risk

HAVAH Wallet sends wallet interaction events (connect, sign, transaction URLs) to Google Analytics with a persistent client UUID.

Each time a user connects a site, signs a message, or sends a transaction, the extension transmits the origin URL and transaction details to Google Analytics GA4 endpoints. A persistent client UUID stored in chrome.storage.local ties these events together across sessions, allowing cross-session linkage of wallet activity to a single user.

HAVAHv2.3.7Chrome Web Store
45Risk

AI-generated. Findings may contain errors. Those marked Verified have been manually reviewed.

Publishers can request a review.

Findings

SeverityMEDIUM
ClassUNWANTED
TypeUnexpected
CWECWE-359
SourceAI SANDBOX

Wallet action origins sent to Google Analytics GA4

Connecting HAVAH Wallet to a site, signing, or requesting a transaction sends the site host to GA4, with a persistent client UUID and session ID.

A GA4 request with those IDs was observed, but no action event body was captured.

01EvidenceCAUSE EFFECT
What actually happens
You did this

You use HAVAH Wallet to connect to a site, sign data, or request a transaction.

The extension did this

The extension sends the site host for that wallet action to Google Analytics GA4.

02EvidenceFIELD TABLE
Fields the code places in wallet-action analytics events
FieldValueWhy it matters
Wallet-action site
havah.ioShows which site was involved when you connected the wallet, signed data, or requested a transaction.
Wallet action type
transactionShows whether the event came from a connect, signing, or transaction request.
Network name
VEGANETAdds which HAVAH network was active for transaction telemetry.
API method
icx_sendTransactionAdds the transaction request method for transaction telemetry.
Client identifier
8f2a4e1c-0b6e-4a10-9f9f-2d6a7b8c1e53Lets analytics link wallet events from the same browser profile over time.
Session identifier
1720837354123Groups wallet activity that happens in the same browser session.
03EvidenceNETWORK CAPTURE
Captured request
POSThttps://www.google-analytics.com/mp/collect?measurement_id=G-2BXRDK4436&api_secret=<redacted>
04EvidenceCODE COMPARE
The code that does this

Analytics sender adds persistent IDs and posts to GA4

What it actually does
Readable analytics sender from the deobfuscated background scriptdeobfuscated/background.js
const google_analytics = new class {
  network = "";
  constructor(debug = !1) {
    this.debug = debug
  }
  async getOrCreateClientId() {
    let {
      clientId
    } = await chrome.storage.local.get("clientId");
    return clientId || (clientId = self.crypto.randomUUID(), await chrome.storage.local.set({
      clientId
    })), clientId
  }
  async getOrCreateSessionId() {
    let {
      sessionData
    } = await chrome.storage.session.get("sessionData");
    const currentTimeInMs = Date.now();
    if (sessionData && sessionData.timestamp) {
      (currentTimeInMs - sessionData.timestamp) / 6e4 > 30 ? sessionData = null : (sessionData.timestamp = currentTimeInMs, await chrome.storage.session.set({
        sessionData
      }))
    }
    return sessionData || (sessionData = {
      session_id: currentTimeInMs.toString(),
      timestamp: currentTimeInMs.toString()
    }, await chrome.storage.session.set({
      sessionData
    })), sessionData.session_id
  }
  async fireEvent(name, params = {}) {
    params.session_id || (params.session_id = await this.getOrCreateSessionId()), params.engagement_time_msec || (params.engagement_time_msec = 100);
    let MEASUREMENT_ID = "",
      API_SECRET = "";
    switch (this.network) {
      case "VEGANET":
        MEASUREMENT_ID = "G-1M074S78H0", API_SECRET = "<redacted>";
        break;
      case "DENEBNET":
        MEASUREMENT_ID = DENEB_MEASUREMENT_ID, API_SECRET = DENEB_API_SECRET;
        break;
      default:
        MEASUREMENT_ID = "G-2BXRDK4436", API_SECRET = "<redacted>"
    }
    try {
      await fetch(`${this.debug?"https://www.google-analytics.com/debug/mp/collect":"https://www.google-analytics.com/mp/collect"}?measurement_id=${MEASUREMENT_ID}&api_secret=${API_SECRET}`, {
        method: "POST",
        body: JSON.stringify({
          client_id: await this.getOrCreateClientId(),
          events: [{
            name,
            params
          }]
        })
      });
      if (!this.debug) return
    } catch (e) {
      console.error("Google Analytics request failed with an exception", e)
    }
  }
  async firePageViewEvent(pageTitle, pageLocation, additionalParams = {}, network) {
    return this.network = network, this.fireEvent("page_view", {
      page_title: pageTitle,
      page_location: pageLocation,
      ...additionalParams
    })
  }
  async fireErrorEvent(error, additionalParams = {}) {
    return this.fireEvent("extension_error", {
      ...error,
      ...additionalParams
    })
  }
};
05EvidenceCODE COMPARE
The code that does this

Wallet message handlers pass the site host into analytics events

What it actually does
CONNECT handler sends connect_urldeobfuscated/background.js
const host = message.detail.host;
return GoogleAnalytics.sendEventGoogleAnalytics("connect_url", {
  connect_url: host
}), isAllowSite = !(!accountInfo.allowSites || 0 == accountInfo.allowSites.length) && Array.from(accountInfo.allowSites).includes(host)
SIGN handler sends signdeobfuscated/background.js
const host = message.detail.host;
isAllowSite = !(!accountInfo.allowSites || 0 == accountInfo.allowSites.length) && Array.from(accountInfo.allowSites).includes(host), GoogleAnalytics.sendEventGoogleAnalytics("sign", {
  sign: host
});
SEND_TRANSACTION handler sends transaction, network, and API methoddeobfuscated/background.js
const host = message.detail.host;
isAllowSite = !(!accountInfo.allowSites || 0 == accountInfo.allowSites.length) && Array.from(accountInfo.allowSites).includes(host);
try {
  GoogleAnalytics.sendEventGoogleAnalytics("transaction", {
    transaction: host,
    network,
    api_method: message.detail.body.method
  })
} catch (e) {
  console.error("e", e)
}
06EvidenceTHIRD PARTY LIST
External analytics destination
  • www.google-analytics.com

    Receives GA4 Measurement Protocol POST requests containing the wallet-action event name, event parameters, client UUID, and session ID.

Data recipients

www.google-analytics.com
Updated 17 September 2026cnncmdhjacpkmjmkcafchppbnpnhdmon