Is Cute Cursors - Custom Cursor for Chrome™ safe?
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.
Who publishes itCute Cursors - 1 other listing from the same operator, none carrying a finding
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.
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
You install Cute Cursors or start a browser session with it enabled.
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.
200 OK. No body was recorded for this GET; the persistent user ID was embedded as the final URL path segment.
- Extension runtime IDcaonbkfnbilglhkejkkekkdicbhgfeje
This identifies which Chrome extension context made the config request.
- Stored browser-installation IDeNFb88cPS6XVkn
This lets the server recognize the same browser installation across separate startup requests.
- HTTP methodGET
The identifier is sent in the URL of a normal config fetch, not in a submitted form body.
The shipped code stores an ID, then sends it in the config URL
Mutation that stores the user ID
background.jsSET_USER_ID: function(t, c) { t.storage.user ? t.storage.user.id = c : t.storage.user = { id: c }}Alphanumeric ID generator
background.jsfunction 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.jsvar 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.jsObject(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.jskey: "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()) }))}- 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
You install the extension or start a browser session with it enabled.
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.
Observed during dynamic analysis as a startup request; no request body was recorded for this GET.
- Extension runtime IDoaikhajndgbglhocphkbjbpioaoejkdl
This identifies which Chrome extension installation context made the config request.
- Stored user IDbKtp5bOo7p97ym
This lets the config server recognize the same browser installation when the request runs again.
- Earlier stored ID observationF8cfdt4nc742MN
A separate analysis session showed the same storage field can persist independently of a single request capture.
The extension keeps a generated ID locally, so later sessions can send the same installation-specific value again.
- Location
- chrome.storage.local user.id
{ "user": { "id": "F8cfdt4nc742MN" }}The shipped code stores an ID, then sends it in the config URL
Mutation that stores the user ID
background.jsSET_USER_ID: function(t, c) { t.storage.user ? t.storage.user.id = c : t.storage.user = { id: c }}Alphanumeric ID generator
background.jsfunction 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.jsvar 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.jsObject(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.jskey: "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()) }))}- 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
You open Chrome with the Cute Cursors extension installed.
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.
HTTP 200. Returns JSON config including cuteTabExtensionId and edge_cuteTabExtensionId for companion-extension linking. Captured during dynamic analysis.
- Pragma
- no-cache
- Expires
- 0
- Cache-Control
- no-cache
- Your persistent user IDInTVbPxokr8e9x
A random alphanumeric string generated once and kept forever. Sent every browser start, letting the server track you across sessions.
- Extension runtime IDlglcggnhgchbapmekdghkkbdmfimkjhi
The stable Chrome-assigned identifier for this extension install. Appears in the URL path alongside your user ID.
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'
{ "user": { "id": "InTVbPxokr8e9x" }}UUID generation and startup dispatch, shipped vs deobfuscated
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); });}- 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.
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.
- mitmproxy (pip install mitmproxy)
- Chrome with Cute Cursors extension installed
- System or browser configured to use proxy at 127.0.0.1:8080
#!/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- 1Install mitmproxy (pip install mitmproxy).
- 2Set browser proxy to 127.0.0.1:
- 38
- 40
- 58
- 6
- 7Run chmod +x cute-cursors-id-detector.sh && ./cute-cursors-id-detector.sh.
- 8Launch Chrome.
- 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