Is CrxMouse: Mouse Gestures safe?

Medium risk

CrxMouse is medium risk. Installing CrxMouse v7.0.5+ and stepping through welcome screens posts your device ID, browser type/version, OS, screen size, A/B variants to Mixpanel, no consent prompt. Gate is whether the Mixpanel token is present. Absent in v6.4.2.…

52Risk

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

Publishers can request a review.

Findings

Device Fingerprint Posted to Mixpanel on Install (v7.0.5+)

Installing CrxMouse v7.0.5+ and stepping through welcome screens posts your device ID, browser type/version, OS, screen size, A/B variants to Mixpanel, no consent prompt.

Gate is whether the Mixpanel token is present.

Absent in v6.4.2.

Severity
Medium unwanted
Type
Unexpected
CWE
CWE-359
Source
Dynamic sandbox
What actually happens
You did this

You install CrxMouse v7.0.5 or open your browser with the extension installed.

Additional events fire each time you navigate a welcome or permission screen during the onboarding flow.

The extension did this

The extension posts your persistent device ID, browser fingerprint, screen size, and experiment assignments to api.mixpanel.com without a consent prompt.

The Mixpanel project token (Yi = f95c8e6a85eba548d664b71b7ebe0ee6) is hardcoded in service_worker.js. The send gate Ji = Boolean(Yi) evaluates to true at parse time and is not user-controllable. The ip=1 query parameter instructs Mixpanel to record the sender's IP for geolocation.

Captured request
POSThttps://api.mixpanel.com/track?ip=1

200 OK; Mixpanel accepted the event.

Headers
accept
text/plain
content-type
application/json
Body
[  {    "event": "Welcome Page",    "properties": {      "token": "f95c8e6a85eba548d664b71b7ebe0ee6",      "$device_id": "e7a3b1f2-c94d-4812-b6e5-2f0d8a7c3e91 (illustrative)",      "$user_id": "e7a3b1f2-c94d-4812-b6e5-2f0d8a7c3e91 (illustrative)",      "$browser": "Chrome",      "$browser_version": "148.0.0.0",      "$version": "7.0.5",      "$os": "Linux",      "$screen_width": 1280,      "$screen_height": 800,      "SP Platform": "Extension",      "SP Permissions": [],      "SP Has Mouse Gestures Enabled": true,      "SP Experiments": [        "New Redesign"      ],      "SP Feature flags": {        "default_cheese_cursor": "false",        "smart_swipe_modal": "true"      },      "crx_event_key": "onboarding_welcome_page_view"    }  }]
Properties in each Mixpanel /track?ip=1 POST (Hi() base properties, service_worker.js:4679-4722)
  • Persistent device ID
    e7a3b1f2-c94d-4812-b6e5-2f0d8a7c3e91

    A stable UUID created on first install and reused every session, letting Mixpanel link every future event to this browser install.

  • User ID
    e7a3b1f2-c94d-4812-b6e5-2f0d8a7c3e91

    Set to the same value as the device ID on first install and can be updated if the extension later links an account.

  • Sender IP address (via ip=1)
    203.0.113.42

    Mixpanel is instructed to record and geolocate the IP of each request, associating your approximate location with your device ID.

  • Browser name and version
    Chrome 148.0.0.0

    The browser name and full version are included in every event, narrowing which device you are using.

  • Operating system
    Linux

    Your operating system name is transmitted as part of the device fingerprint.

  • Screen dimensions
    1280 × 800

    Your screen's pixel width and height are included, further contributing to device fingerprinting.

  • A/B experiment assignments
    New Redesign: enrolled

    The GrowthBook experiment variants and feature flags active on your installation are sent to Mixpanel on every onboarding event.

  • Extension version
    7.0.5

    The installed extension version is included in every Mixpanel event.

The code that does this

Qi(), Mixpanel track sender with hardcoded token and ip=1 parameter (service_worker.js:4751-4777)

Readable version
// service_worker.js:4751-4777const Ki = 'https://api.mixpanel.com';const Yi = 'f95c8e6a85eba548d664b71b7ebe0ee6'; // hardcoded Mixpanel project tokenconst Ji = Boolean(Yi); // evaluates true at parse time — the only send gateconst Qi = async (eventKey, eventData, { signal, plugin } = {}) => {  if (!Ji) return; // gate: token is non-empty string, not user consent  const { mixpanelEventName, props = {} } = zi(eventKey, eventData, plugin);  if (!(props.$user_id || props.$device_id)) throw new Error('user id missing');  const body = JSON.stringify([{    event: mixpanelEventName,    properties: {      token: Yi,      ...props  // base props from Hi() + event-specific fields    }  }]);  await fetch(`${Ki}/track?ip=1`, { // ip=1: instruct Mixpanel to geolocate sender IP    signal,    method: 'POST',    headers: { accept: 'text/plain', 'content-type': 'application/json' },    body  });};
Destination receiving device fingerprint and onboarding events
    • api.mixpanel.com

    Mixpanel Inc. (Twilio). Receives onboarding events and device fingerprint via /track?ip=1 and /engage#profile-set. ip=1 makes Mixpanel record the originating IP for geolocation.

Browsing History Sent to Developer Server on Opt-In

With opt-in 'Mouse Gestures' enabled, CrxMouse records every visited URL, the referrer, redirect chain, and link clicks, POSTing to api.mousegesturesapi.com.

DA confirmed a planted URL sent across navigations to several major sites.

Severity
Medium unwanted
Type
Unexpected
CWE
CWE-359
Source
Dynamic sandbox
What actually happens
You did this

You navigate to any web page after enabling the opt-in tracking feature.

The 'optedin' flag in chrome.storage.sync must be truthy for data to be transmitted.

The extension did this

The extension POSTs the page URL, referring URL, redirect chain, and external link clicks to api.mousegesturesapi.com.

The payload is assembled by SendRequestForTab._getTabData() and dispatched via a CustomEvent to As(), which performs the fetch call with the packed body.

Captured request
POSThttps://api.mousegesturesapi.com/ms/gs

200 OK; response body contains gesture data for the visited domain.

Headers
xr3r
1715428800000
Content-Type
application/x-www-form-urlencoded
Body
q=https%3A%2F%2Fen.wikipedia.org%2Fwiki%2Fen.wikipedia.org/wiki/CrxMouse&prev=https%3A%2F%2Fwww.google.com%2F&hreferer=https%3A%2F%2Fwww.google.com%2F&tt=link&ts=1715428800000
Fields assembled by SendRequestForTab._getTabData()
  • Current page URL
    https://app.example.com/dashboard?token=abc123

    The full URL of every page you visit is recorded and transmitted.

  • Previous page URL
    https://www.google.com/search?q=example

    The URL you navigated from, revealing your browsing sequence.

  • Referrer header
    https://www.google.com/search?q=sensitive+query

    The HTTP referrer sent by the browser, which can include search query parameters.

  • External link clicks
    https://accounts.google.com/signin

    Links you clicked that took you to a different domain are recorded.

  • Redirect chain
    http://example.com/ → https://example.com/

    Server and client-side redirects along the navigation path.

  • Timestamp
    1715428800000

    The precise time of each navigation, allowing a timeline of your activity to be reconstructed.

The code that does this

Top-level CustomEvent listener that gates sending on opt-in status

Readable version
// service_worker.js:5637-5639self.addEventListener('cpw2ef3k', async t => {  // 'p' is the 'optedin' storage key (line 312)  await e.get(p) && As(t.detail)  // only sends if optedin is truthy});
Destinations receiving browsing history data
    • api.mousegesturesapi.com

    Developer-controlled analytics server for crxMouse. Receives browsing history (URL, referrer, redirects, link clicks) via /ms/gs. Overrideable via Proconstantinator config.

Daily Telemetry Beacon Sent to Google Analytics Without Opt-In Gate

Once a day, CrxMouse sends a 'LIVE' event to Google Analytics regardless of opt-in tracking, including a stable UUID, browser/OS details, screen size, version, GrowthBook assignments.

DA confirmed it fires unconditionally every 24 hours.

Severity
Medium unwanted
Type
Unexpected
CWE
CWE-200
Source
Dynamic sandbox
What actually happens
You did this

You use your browser normally, no special action required.

The beacon fires once per 24 hours driven solely by a time check on tab navigation, regardless of opt-in status.

The extension did this

The extension sends your persistent client ID, browser fingerprint, and A/B experiment assignments to Google Analytics.

ns('LIVE') runs the full telemetry pipeline: builds the event body with userId, browser, OS, screen dimensions, and GrowthBook experiment data, then POSTs to google-analytics.com/mp/collect using hardcoded credentials decoded from Base64.

When this fires

Every 1 day

The LIVE beacon fires at most once per 24 hours. The time of last send is stored in chrome.storage.sync under 'lastLiveSent'. The check runs on every chrome.tabs.onUpdated event with status='complete'.

Captured request
POSThttps://www.google-analytics.com/mp/collect?measurement_id=G-DTT9W53FG1&api_secret=<redacted>

204 No Content (GA4 Measurement Protocol accepted).

Headers
Content-Type
application/json
Body
{  "client_id": "cc2feb24-4f66-4189-adc2-4d5ea868fd59",  "events": [    {      "name": "LIVE",      "params": {        "optin": true,        "SP Platform": "Extension",        "SP Has Mouse Gestures Enabled": true,        "SP Experiments": [          "redesign"        ],        "SP Feature flags": {          "default_cheese_cursor": "false",          "smart_swipe_modal": "true"        }      }    }  ]}
Why you can't catch this in DevTools

The GA4 measurement ID and API secret are stored as Base64-encoded strings in service_worker.js and decoded at runtime using a custom decoder function qi().

Decoded value
Measurement ID: G-DTT9W53FG1API Secret: -uexKC9OT9ulAlBqGI15KA
Data included in every LIVE beacon (Vi constant, line ~4634)
  • Persistent client ID
    cc2feb24-4f66-4189-adc2-4d5ea868fd59

    A stable UUID created on first install and stored locally, letting Google Analytics link every daily beacon to this install for years.

  • Browser and version
    Chrome 124.0.6367.60

    The name and version of your browser, contributing to a device fingerprint.

  • Operating system
    Windows

    Your OS is transmitted alongside the browser details.

  • Screen dimensions
    1920 × 1080

    Your screen width and height are included, further narrowing device fingerprinting.

  • A/B experiment assignments
    redesign: true, smart_swipe_modal: true

    The GrowthBook feature flags and experiment variants active on your installation are reported, revealing which UI tests you are enrolled in.

  • Extension version and install channel
    7.0.5 / web_store

    The installed version and the channel through which the extension was installed.

The code that does this

LIVE beacon trigger, no optedin check at this call site

Readable version
// service_worker.js:5635 — inside chrome.tabs.onUpdated listener// S = 'LIVE' (line 322); e = chrome.storage.sync accessor// p = 'optedin' key — NOT checked hereconst lastSent = await e.get('lastLiveSent') || 0;const now = Date.now();if (now - lastSent > 86400000) {  await e.set('lastLiveSent', now);  ns('LIVE');  // fires regardless of opt-in status}
Recipients of the daily LIVE beacon
    • www.google-analytics.com

    Google Analytics 4 (Measurement Protocol). Receives the LIVE event via /mp/collect with the hardcoded measurement ID and API secret. Google's retention policies apply.

+1 more finding not shown

Our write-ups

Updated 30 September 2026jlgkpaicikihijadgifklkbpdajbkhjo