Is Cute Cursors - Custom Cursor for Chrome™ safe?

Medium risk

This extension replaces the default Chrome pointer with customizable cursors for web browsing.

Users can choose cursor styles such as comics, anime, kawaii, and video game designs, adjust cursor size, and manage favorites from the extension popup. The store description also says users can select from a cursor library or create a custom cursor with a built-in editor.

Cute Cursorsv4.1.4Chrome Web Store
54Risk
Who publishes it

Cute Cursors - 1 other listing from the same operator, none carrying a finding

What this publisher told the store about itself, and the other listings that told it the same thing.

Publisher
Cute Cursors

Same store account

1 other listing published from this account, 40k+ users between them, none of them carrying a finding.

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

Publishers can request a review.

Findings

Persistent browser ID sent on extension startup

When Cute Cursors starts, it sends a GET to cp.cute-cursors.com with a persistent alphanumeric ID tied to the browser install.

Dynamic analysis captured the request; the user ID is the final URL path segment.

No request body was recorded.

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

You install Cute Cursors or start a browser session with it enabled.

The extension did this

Cute Cursors sends its startup config request with a stored browser-installation ID in the URL.

The captured request happened before any user interaction in the test session.

Captured request
GEThttps://cp.cute-cursors.com/api/v2/config/caonbkfnbilglhkejkkekkdicbhgfeje/eNFb88cPS6XVkn

200 OK. No body was recorded for this GET; the persistent user ID was embedded as the final URL path segment.

Fields sent in the captured startup request
  • Extension runtime ID
    caonbkfnbilglhkejkkekkdicbhgfeje

    This identifies which Chrome extension context made the config request.

  • Stored browser-installation ID
    eNFb88cPS6XVkn

    This lets the server recognize the same browser installation across separate startup requests.

  • HTTP method
    GET

    The identifier is sent in the URL of a normal config fetch, not in a submitted form body.

The code that does this

The shipped code stores an ID, then sends it in the config URL

Readable version

Mutation that stores the user ID

background.js
SET_USER_ID: function(t, c) {  t.storage.user ? t.storage.user.id = c : t.storage.user = {    id: c  }}

Alphanumeric ID generator

background.js
function e() {  for (var t = Math.random().toString(36).substr(2, 9) + Date.now().toString(36).substr(3), c = 0; c < t.length; c++) Math.random() > .5 && (t = t.substr(0, c) + t[c].toUpperCase() + t.substr(c + 1));  return t}

API base URL

background.js
var n = r(21),  s = "https://cute-cursors.com/",  e = "https://cp.cute-cursors.com/api/",  a = n.a.create({    baseURL: "https://cp.cute-cursors.com/api"  });a.interceptors.response.use((function(t) {  return 204 === t.status && (t.data = null), t}), (function(t) {  return Promise.reject(t)}))

Startup config request

background.js
Object(l.b)().then((function() {  var t;  null !== (t = l.a.state.storage.user) && void 0 !== t && t.id || l.a.commit("SET_USER_ID", Object(o.b)()), c.getConfig(), l.a.state.storage.cuteTabConnected && c.initManagementListeners()}))

Config URL construction

background.js
key: "getConfig",value: function() {  var t = this;  i.a.get(d.b + "v2/config/".concat(chrome.runtime.id, "/").concat(l.a.state.storage.user.id), {    headers: {      "Cache-Control": "no-cache",      Pragma: "no-cache",      Expires: "0"    }  }).then((function(c) {    t.cuteTabExtensionId = c.data.cuteTabExtensionId, "chrome" === t.browser && (t.cuteTabExtensionId = c.data.cuteTabExtensionId), "edge" === t.browser && (t.cuteTabExtensionId = c.data.edge_cuteTabExtensionId), l.a.commit("SET_REMOTE_CONFIG", c.data), t.initManagementListeners(), setTimeout((function() {      t.connectToCuteTab()    }), 2e3)  })).catch((function(c) {    l.a.state.storage.remoteConfig && ("chrome" === t.browser && (t.cuteTabExtensionId = l.a.state.storage.remoteConfig.cuteTabExtensionId), "edge" === t.browser && (t.cuteTabExtensionId = l.a.state.storage.remoteConfig.edge_cuteTabExtensionId), t.connectToCuteTab())  }))}
Remote host contacted by the startup config request
    • cp.cute-cursors.com

    Cute Cursors control-panel API host that receives the startup config request containing the runtime extension ID and stored user ID.

Startup config request sends a stored user ID

When the extension starts, it creates or reuses a stored user ID and sends it to cp.cute-cursors.com in the startup config request.

The extension ID and user ID appear in the URL path; storage evidence shows the ID persists across sessions.

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

You install the extension or start a browser session with it enabled.

The extension did this

The extension sends a config request that includes its runtime ID and a stored user ID.

The request is sent to cp.cute-cursors.com without asking for an extra action.

Captured request
GEThttps://cp.cute-cursors.com/api/v2/config/oaikhajndgbglhocphkbjbpioaoejkdl/bKtp5bOo7p97ym

Observed during dynamic analysis as a startup request; no request body was recorded for this GET.

Fields sent in the startup config URL
  • Extension runtime ID
    oaikhajndgbglhocphkbjbpioaoejkdl

    This identifies which Chrome extension installation context made the config request.

  • Stored user ID
    bKtp5bOo7p97ym

    This lets the config server recognize the same browser installation when the request runs again.

  • Earlier stored ID observation
    F8cfdt4nc742MN

    A separate analysis session showed the same storage field can persist independently of a single request capture.

What's stored on your device

The extension keeps a generated ID locally, so later sessions can send the same installation-specific value again.

Location
chrome.storage.local user.id
Contents
{  "user": {    "id": "F8cfdt4nc742MN"  }}
The code that does this

The shipped code stores an ID, then sends it in the config URL

Readable version

Mutation that stores the user ID

background.js
SET_USER_ID: function(t, c) {  t.storage.user ? t.storage.user.id = c : t.storage.user = {    id: c  }}

Alphanumeric ID generator

background.js
function e() {  for (var t = Math.random().toString(36).substr(2, 9) + Date.now().toString(36).substr(3), c = 0; c < t.length; c++) Math.random() > .5 && (t = t.substr(0, c) + t[c].toUpperCase() + t.substr(c + 1));  return t}

API base URL

background.js
var n = r(21),  s = "https://cute-cursors.com/",  e = "https://cp.cute-cursors.com/api/",  a = n.a.create({    baseURL: "https://cp.cute-cursors.com/api"  });a.interceptors.response.use((function(t) {  return 204 === t.status && (t.data = null), t}), (function(t) {  return Promise.reject(t)}))

Startup config request

background.js
Object(l.b)().then((function() {  var t;  null !== (t = l.a.state.storage.user) && void 0 !== t && t.id || l.a.commit("SET_USER_ID", Object(o.b)()), c.getConfig(), l.a.state.storage.cuteTabConnected && c.initManagementListeners()}))

Config URL construction

background.js
key: "getConfig",value: function() {  var t = this;  i.a.get(d.b + "v2/config/".concat(chrome.runtime.id, "/").concat(l.a.state.storage.user.id), {    headers: {      "Cache-Control": "no-cache",      Pragma: "no-cache",      Expires: "0"    }  }).then((function(c) {    t.cuteTabExtensionId = c.data.cuteTabExtensionId, "chrome" === t.browser && (t.cuteTabExtensionId = c.data.cuteTabExtensionId), "edge" === t.browser && (t.cuteTabExtensionId = c.data.edge_cuteTabExtensionId), l.a.commit("SET_REMOTE_CONFIG", c.data), t.initManagementListeners(), setTimeout((function() {      t.connectToCuteTab()    }), 2e3)  })).catch((function(c) {    l.a.state.storage.remoteConfig && ("chrome" === t.browser && (t.cuteTabExtensionId = l.a.state.storage.remoteConfig.cuteTabExtensionId), "edge" === t.browser && (t.cuteTabExtensionId = l.a.state.storage.remoteConfig.edge_cuteTabExtensionId), t.connectToCuteTab())  }))}
Remote host contacted by the startup config request
    • cp.cute-cursors.com

    Cute Cursors control-panel API host that receives the startup config request containing the runtime extension ID and stored user ID.

Persistent User ID Sent to Cute Cursors Server on Every Browser Start

Every time Chrome starts, Cute Cursors assigns your browser a permanent random ID and sends it to its own server in a config request, automatically.

The ID persists in chrome.storage.local, letting the server correlate your activity.

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

You open Chrome with the Cute Cursors extension installed.

The extension did this

The extension sends a GET request to cp.cute-cursors.com carrying your permanent browser-unique ID in the URL path.

The request fires automatically on every browser start, with no user interaction required.

Captured request
GEThttps://cp.cute-cursors.com/api/v2/config/lglcggnhgchbapmekdghkkbdmfimkjhi/InTVbPxokr8e9x

HTTP 200. Returns JSON config including cuteTabExtensionId and edge_cuteTabExtensionId for companion-extension linking. Captured during dynamic analysis.

Headers
Pragma
no-cache
Expires
0
Cache-Control
no-cache
What the config request exposes to cp.cute-cursors.com
  • Your persistent user ID
    InTVbPxokr8e9x

    A random alphanumeric string generated once and kept forever. Sent every browser start, letting the server track you across sessions.

  • Extension runtime ID
    lglcggnhgchbapmekdghkkbdmfimkjhi

    The stable Chrome-assigned identifier for this extension install. Appears in the URL path alongside your user ID.

What's stored on your device

Written to chrome.storage.local on first run, persists until uninstall. Every later browser start reads and sends this value outbound.

Location
chrome.storage.local key 'user'
Contents
{  "user": {    "id": "InTVbPxokr8e9x"  }}
The code that does this

UUID generation and startup dispatch, shipped vs deobfuscated

Readable version

Persistent user ID generator

// Generates a random alphanumeric string on first run.// Result is mixed-case (random per-char): e.g. 'InTVbPxokr8e9x'function generateUserId() {  var t = Math.random().toString(36).substr(2, 9) + Date.now().toString(36).substr(3);  for (var c = 0; c < t.length; c++) {    if (Math.random() > 0.5) {      t = t.substr(0, c) + t[c].toUpperCase() + t.substr(c + 1);    }  }  return t; // e.g. 'InTVbPxokr8e9x'}

Startup: assign ID if not set, then fetch config

// Runs once on every browser start.// If no user.id exists in storage, generate and persist one.if (!store.state.storage.user?.id) {  store.commit('SET_USER_ID', generateUserId());}// Always fires the config fetch — user ID goes in the URL path.this.getConfig();

getConfig — embeds user ID in URL path

getConfig() {  // URL: https://cp.cute-cursors.com/api/v2/config/<extension_id>/<user_id>  axios.get(API_BASE + 'v2/config/' + chrome.runtime.id + '/' + store.state.storage.user.id, {    headers: { 'Cache-Control': 'no-cache', Pragma: 'no-cache', Expires: '0' }  }).then(response => {    store.commit('SET_REMOTE_CONFIG', response.data);    this.initManagementListeners();    setTimeout(() => this.connectToCuteTab(), 2000);  });}
Where the persistent user ID is sent
    • cp.cute-cursors.com

    First-party API server for Cute Cursors. Receives the persistent user ID and extension runtime ID on every browser startup via the /api/v2/config/ endpoint.

Check if you're affected

Uses mitmproxy or a network capture tool to confirm whether your browser sends the persistent user ID to cp.cute-cursors.com on startup. Prints the captured URL path showing the user ID segment.

Requires
  • mitmproxy (pip install mitmproxy)
  • Chrome with Cute Cursors extension installed
  • System or browser configured to use proxy at 127.0.0.1:8080
cute-cursors-id-detector.sh · sh
#!/usr/bin/env sh# cute-cursors-id-detector.sh# Confirms whether Cute Cursors sends a persistent user ID on Chrome startup.# Requires: mitmproxy (pip install mitmproxy) and a browser with the extension.## Usage:#   chmod +x cute-cursors-id-detector.sh#   ./cute-cursors-id-detector.shset -ePORT=8080LOG=/tmp/cute_cursors_capture.txtecho "[*] Starting mitmproxy on port $PORT — launch Chrome with Cute Cursors installed."echo "[*] Output: $LOG"echo "[*] Press Ctrl+C after Chrome has started and the config request has fired."# Run mitmproxy in flow-dump mode; filter for cp.cute-cursors.commitmproxy --listen-port "$PORT" --mode regular --quiet \  --set flow_detail=2 \  --anticache \  --set view_filter='~d cp.cute-cursors.com' \  --save-stream-file "$LOG" &MITM_PID=$!trap "kill $MITMPROXY_PID 2>/dev/null; exit" INTwait $MITM_PIDecho "\n[*] Requests to cp.cute-cursors.com:"grep -o 'v2/config/[^"]*' "$LOG" 2>/dev/null || echo "No matching requests captured yet. Re-run and let Chrome fully start."# Expected output:# v2/config/<extension_runtime_id>/<persistent_user_id># e.g.: v2/config/lglcggnhgchbapmekdghkkbdmfimkjhi/InTVbPxokr8e9x
How to run it
  1. 1Install mitmproxy (pip install mitmproxy).
  2. 2Set browser proxy to 127.0.0.1:
  3. 38
  4. 40
  5. 58
  6. 6
  7. 7Run chmod +x cute-cursors-id-detector.sh && ./cute-cursors-id-detector.sh.
  8. 8Launch Chrome.
  9. 9Script prints user ID as the segment after /config/.

What it can do

Permissions this extension asks for, as declared in version 4.1.6. Asking for a permission is not a finding on its own - it is what the extension can do if it chooses to. The current listed version is 4.1.4, which we have not unpacked yet.

  • Read and change your data on every site you visit

    <all_urls>

  • Store data in your browser

    storage

  • Store an unlimited amount of data in your browser

    unlimitedStorage

  • See the address and title of every tab you have open

    tabs

  • Act on the current tab, but only after you click the extension

    activeTab

  • Run its own code inside the pages you visit

    scripting

Updated 30 September 2026anflghppebdhjipndogapfagemgnlblh