Is IPRoyal Proxy Manager safe?
IPRoyal Proxy Manager sends a persistent device ID and hashed user ID to Google Analytics on login and other events.
Each time a user logs in, the extension generates or retrieves a persistent UUID stored locally and SHA-1-hashes the numeric user ID, then transmits both to Google Analytics via the Measurement Protocol. A content script injected into every page also writes proxy connection state and WebRTC protection status into a hidden DOM element readable by any page script, which also reveals that the extension is installed.
Who publishes itIPRoyal Services FZE LLC - no other listings under this identity, 6 shared hostnames
IPRoyal Services FZE LLC - no other listings under this identity, 6 shared hostnames
What this publisher told the store about itself, and the other listings that told it the same thing.
Shared hosts - 6 hostnames
Hostnames hardcoded in this extension that few other listings call. That can mean one operator behind both, and it can equally mean a small shared vendor, so it is context rather than a conclusion. Hosts that many listings call are left out: they are services, not connections.
AI-generated. Findings may contain errors. Those marked Verified have been manually reviewed.
Publishers can request a review.
Findings
Login analytics sends persistent IDs to Google Analytics
When you sign in through IPRoyal Proxy Manager, the extension hashes the user ID, combines it with a stored client ID, and posts a login event to Analytics.
No login was tested; no body was captured.
Fields below come from the shipped code.
- Severity
- Medium unwanted
- Type
- Unexpected
- CWE
- CWE-359
- Source
- Dynamic sandbox
You sign in through the IPRoyal account flow used by the extension.
The content script listens for the page's login event on IPRoyal domains.
The extension prepares a login analytics event with a persistent client ID and a hashed user ID.
The background service worker calls the analytics sender after hashing the numeric user ID.
- Extension client IDfB2qNw7u93YkLJtGQZx0a (illustrative)
This lets analytics connect multiple extension events from the same browser profile.
- Hashed account ID7c4a8d09ca3762af61e59520943dc26494f8941b (illustrative SHA-1 of a numeric ID)
This ties the login event to your IPRoyal account without sending the raw numeric ID in this request.
- Event namelogin
This records that the event was a sign-in rather than another extension action.
- Analytics destinationwww.google-analytics.com/mp/collect
This shows the outside service that receives the event.
No response was recorded because the authenticated login path was not exercised during testing.
The login event flows from the page to the analytics sender
Content script forwards the page login event
content-scripts/content.jsconst h = { main: () => { window.addEventListener("iproyal-extension-login2", e => { l.runtime.sendMessage({ type: "login", options: e.detail }) }) }, matches: ["https://*.iproyal.com/*", "https://*.iproyal.dev/*", "http://localhost/*"]};Background service worker handles login and starts analytics
background.jsconst o = async e => { if (e.type === "login") { const { options: t } = e; await b.setValue(t.accessToken), r.notifications.create({ type: "basic", title: v(), iconUrl: "icon/48.png", message: C() }), _(t.userId).then(() => E("login", {})) }};r.runtime.onMessage.addListener(e => { const t = e; typeof t == "object" && o(t)})Shared chunk builds the Google Analytics request
chunks/_virtual_wxt-plugins-CEAEBqWF.jsconst me = async n => { const e = new TextEncoder, t = await crypto.subtle.digest("SHA-1", e.encode(n)); return [...new Uint8Array(t)].map(r => r.toString(16).padStart(2, "0")).join("")}, ge = "useandom-26T198340PX75pxJACKVERYMINDBUSHWOLF_GQZbfghjklqvwyzrict";let ye = n => crypto.getRandomValues(new Uint8Array(n)), ve = (n, e, t) => { let r = (2 << Math.log2(n.length - 1)) - 1, c = -~(1.6 * r * e / n.length); return (l = e) => { let u = ""; for (;;) { let p = t(c), d = c | 0; for (; d--;) if (u += n[p[d] & r] || "", u.length >= l) return u } } }, Xe = (n, e = 21) => ve(n, e | 0, ye), Z = (n = 21) => { let e = "", t = crypto.getRandomValues(new Uint8Array(n |= 0)); for (; n--;) e += ge[t[n] & 63]; return e };const we = M.defineItem("local:client-id", { init: () => Z(), fallback: "" }), ee = M.defineItem("session:analytics-user-id"), Ze = async n => { const e = typeof n == "number" ? await me(n.toString()) : void 0; await ee.setValue(e) }, et = async (n, e) => { const t = "G-S5S01SJGJB", r = "<redacted>", c = !!r; if (console.info("[GA]", n, e, c), !c) return; const [l, u] = await Promise.all([we.getValue(), ee.getValue()]); await fetch(`https://www.google-analytics.com/mp/collect?measurement_id=${t}&api_secret=${r}`, { method: "POST", body: JSON.stringify({ client_id: l, user_id: u, events: [{ name: n, params: e }] }) }) }- www.google-analytics.com
Receives the Measurement Protocol POST containing the login event, extension client ID, and hashed account ID.
Where it sends data
Destinations our analysis observed IPRoyal Proxy Manager contacting. Sending data somewhere is not a finding on its own - an extension that syncs your settings has to talk to its own server - but it is where your data can go, and who else it goes to.
- www.google-analytics.comwidely used
IPRoyal Proxy Manager sends data to www.google-analytics.com. A widely used service: 346 other extensions we have analysed send data here.