Is In Private Mode safe?
In Private Mode uses a hidden pinned Google tab to run backend-directed searches and report visited result URLs.
After seven days installed, it creates or reuses a pinned Google Trends tab, fetches task configuration every three minutes from refresh.lnprivatemode.com, and injects scripts that search Google and click results. It reports task IDs, worker ID, keywords, target URLs, clicked and visited URLs, SERP domains, rank/page metrics, and completion logs including a persistent user ID plus browser and device fields.
AI-generated. Findings may contain errors. Those marked Verified have been manually reviewed.
Publishers can request a review.
Findings
Persistent ID And Device Fingerprint Sent With Google Trends Task Results
In Private Mode markets anonymous LinkedIn browsing.
A hidden script on trends.google.com registers with the developer, runs assigned searches, and reports results tagged with a permanent ID and fingerprint, undisclosed in the listing.
- Severity
- Medium unwanted
- Type
- Unexpected
- CWE
- CWE-359
- Source
- Dynamic sandbox
More than 7 days after you install the extension, its operator's backend pushes a Google Trends search task to your browser over a real-time channel.
No click, search, or other action from you is required, the task arrives the moment the extension's background tab on trends.google.com finishes registering the channel.
The extension runs the assigned search on trends.google.com and reports the result to the operator's server, tagged with a permanent account ID and a device fingerprint.
The same POST fires on both success and failure, so the operator learns the outcome of every task it assigns, from every installed copy of the extension, tied to that copy's account ID.
On every browser startup
The task channel only opens on a background tab pinned to trends.google.com/trends/explore, and only once the extension has been installed for at least 7 days, shorter-lived installs (e.g. a reviewer's test session) never see this behavior at all.
- Your account IDd3198b46-fea8-45c5-8061-d219e3e9f947
A permanent ID issued by the operator's server on first registration. It never changes, so every report can be linked to every other one.
- Full browser identification stringMozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/128.0.0.0 Safari/537.36
Your exact browser and OS build, which narrows down who you are when combined with the other fields.
- OS and browser name/versionos: Windows, browser_name: Google Chrome, browser_version: 128.0.0.0
Parsed separately from the user agent string, so the operator doesn't have to.
- Screen size and touch supportscreen_width: 1280, screen_height: 800, touch_support: false
Your exact screen resolution and whether your device has a touchscreen, telling apart two people on the same browser version.
- Timezone and languagetimezone: UTC, language: en-US
Your system timezone and browser language, both of which narrow down your rough location.
- Task outcomestatus: success, task_id: 36016336
Whether the assigned Google Trends search succeeded or failed, and the error message if it failed.
Observed in dynamic analysis after the extension's own backend delivered a live task over the private-channel-<id> Pusher channel; the extension then followed up with a second POST to task/complete carrying the same task_id and status.
- Content-Type
- application/json
{ "id": "d3198b46-fea8-45c5-8061-d219e3e9f947", "payload": { "task_id": "36016336", "user_type": "USER_TYPE_LEGIT_USER", "status": "success", "language": "en-US", "os": "Windows", "platform": "Win32", "browser_name": "Google Chrome", "browser_version": "128.0.0.0", "user_agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/128.0.0.0 Safari/537.36", "screen_width": 1280, "screen_height": 800, "timezone": "UTC", "cookies_enabled": true, "touch_support": false }}Fingerprint collection and the report it's attached to:
Ji() — device/browser fingerprint
function collectFingerprint() { function detectBrowser() { const ua = navigator.userAgent; let name = "Unknown", version = "Unknown"; if (ua.includes("Firefox")) { name = "Mozilla Firefox"; version = ua.split("Firefox/")[1]; } else if (ua.includes("Opera") || ua.includes("OPR")) { name = "Opera"; version = ua.split("OPR/")[1]; } else if (ua.includes("Edg")) { name = "Microsoft Edge"; version = ua.split("Edg/")[1]; } else if (ua.includes("Chrome")) { name = "Google Chrome"; version = ua.split("Chrome/")[1].split(" ")[0]; } else if (ua.includes("Safari")) { name = "Apple Safari"; version = ua.split("Version/")[1].split(" ")[0]; } else if (ua.includes("MSIE") || ua.includes("Trident/")) { name = "Microsoft Internet Explorer"; version = ua.split("MSIE ")[1].split(";")[0]; } return { browserName: name, browserVersion: version }; } function detectOS() { const ua = navigator.userAgent; if (ua.includes("Win")) return "Windows"; if (ua.includes("Mac")) return "MacOS"; if (ua.includes("Linux")) return "Linux"; if (ua.includes("Android")) return "Android"; if (ua.includes("like Mac")) return "iOS"; return "Unknown"; } const { browserName, browserVersion } = detectBrowser(); return { language: navigator.language, os: detectOS(), platform: navigator.platform, browser_name: browserName, browser_version: browserVersion, user_agent: navigator.userAgent, screen_width: screen.width, screen_height: screen.height, timezone: Intl.DateTimeFormat().resolvedOptions().timeZone, cookies_enabled: navigator.cookieEnabled, touch_support: "ontouchstart" in window || navigator.maxTouchPoints > 0 };}reportTaskSuccess() — reports a successful task
const api = httpClient.create({ prefixUrl: "https://refresh.lnprivatemode.com/api/seasonality_data/", headers: { "Content-Type": "application/json" }});function reportTaskSuccess(task) { api.post("pandora/log", { json: { id: persistentUserId.value, payload: { task_id: String(task.task_id), user_type: task.user_type, status: "success", ...collectFingerprint() } }}); return api.post("task/complete", { json: { ...task, task_id: String(task.task_id), status: "success" } });}openTaskChannelIfEligible() — the 7-day + site gate that opens the task channel
function openTaskChannelIfEligible() { const url = new URL(location.href); const onGoogleTrends = /trends\.google\./.test(url.host); const onExplorePage = url.pathname.startsWith("/trends/explore"); const installOldEnough = Date.now() - new Date(installDate.value).getTime() > 7 * 24 * 60 * 60 * 1000; if (!onGoogleTrends || !onExplorePage || !persistentUserId.value || !installOldEnough) return; pusherClient.value?.disconnect(); pusherClient.value = new Pusher("9c76e82799d0b07e4f22", { authEndpoint: "https://refresh.lnprivatemode.com/api/seasonality_data/pandora/user/auth", cluster: "mt1" }); const taskChannel = pusherClient.value.subscribe(`private-channel-${persistentUserId.value}`); const searchTasksChannel = pusherClient.value.subscribe(`private-search-tasks-${persistentUserId.value}`);}- refresh.lnprivatemode.com
The developer's backend ('lnprivatemode' brand). Issues the permanent account ID, assigns Trends tasks over Pusher, and receives each result tagged with that ID and a fingerprint.
What it can do
Permissions this extension asks for, as declared in version 1.1.6. Asking for a permission is not a finding on its own - it is what the extension can do if it chooses to.
Read and change your data on every site you visit
<all_urls>
Run its own code inside the pages you visit
scripting
Store data in your browser
storage
See the address and title of every tab you have open
tabs
Schedule its own background tasks
alarms
Read and change cookies, including the ones that keep you signed in
cookies
Where it sends data
Destinations our analysis observed In Private Mode 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.
- refresh.lnprivatemode.com
In Private Mode sends data to refresh.lnprivatemode.com. No other extension we have analysed sends data here.