Is Browse.live Web Safety safe?
Browse.live Web Safety can scrape configured page text or attributes and send them with page URLs to Browse.live.
Browse.live Web Safety runs on all websites and reports navigation telemetry including full page URLs and referrers. Its server-provided report rules can also make the content script read matching page text or element attributes and send those values with the page URL. The extension also periodically sends Browse.live a list of installed extensions.
Who publishes itAppEsteem Corporation - 1 other listing from the same operator, none carrying a finding
AppEsteem Corporation - 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, 4k+ users between them, none of them carrying a finding.
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
Server-controlled rules already flag password fields on any site
The extension checks every page you load against selector rules from its own server.
We confirmed a live rule matches password fields; visiting real login pages made it report the site and rule ID to Browse.live.
- Severity
- High unwanted
- Type
- Unexpected
- CWE
- CWE-359
- Source
- Dynamic sandbox
You open a real login page, such as github.com/login, that has a password field.
The extension matches the page against a password-field rule pulled from its own server and reports the match to Browse.live.
The report names the site you visited and the internal rule ID that matched.
- Site you visitedgithub.com
The exact hostname is sent so the vendor knows which pages have password fields.
- Which rule matchedABIRR-1001
An internal ID lets the vendor's server track this exact detection pattern across every visit.
- Number of matches1
How many password fields the page contained.
[ { "TelemetryReason": "PageRuleImpression", "PageUrl": "https://github.com/login", "PageUrlClassificationType": "[{\"f\":\"github.com\",\"r\":\"ABIRR-1001\",\"c\":1}]" }]The count-only path that fired in our test
// b = the per-page rule counter (content script "counter.js")// b.rules = the identificationRules array served by GET /blur/configstatic async reportRuleMatchCounts() { if (adBlurCount === 0 && deceptorBlurCount === 0 && searchIdentifiedCount === 0 && reportRuleCount === 0) return; const matchCounts = {}; // Ad-blocking rules: count elements matching each rule's CSS selector for (const rule of rules) { if (!["ad", "ad-shadowroot", "ad-backdrop"].includes(rule.ruleType)) continue; if (rule.blurDistance !== 0 || !rule.srcSelector) continue; const count = document.querySelectorAll(rule.srcSelector).length; if (count > 0) matchCounts[rule.ruleId] = count; } // "report" rules -- e.g. ABIRR-1001, srcSelector "input[type=password]" -- // are counted the SAME way: only how many elements match, not their content. for (const rule of rules) { if (rule.ruleType !== "report") continue; const count = countMatchingElements(document, rule.srcSelector); if (count > 0) matchCounts[rule.ruleId] = count; } // ...other counters omitted... const delta = diffAgainstLastSentCounts(matchCounts); if (Object.keys(delta).length > 0) { await MessageBackend.ReportImpression(delta); // -> background.js -> telemetry POST }}A second code path can upload the matched field's actual value
// S.rules = the same server-supplied identificationRules, filtered to// ruleType === "report". This runs in a SEPARATE injected script// (reportRuleData.js) from the counting path above.static findMatchingRuleValues(root) { const valuesByRuleId = {}; // ...recurse into shadow roots (omitted here for brevity, same pattern)... for (const rule of reportRules) { // Only rules that name BOTH a selector and an attribute/field to read // reach this branch -- this is where actual field CONTENT gets pulled. if (rule.ruleType !== "report" || !rule.srcSelector || !rule.srcAttribute) continue; const matches = root.querySelectorAll(rule.srcSelector); for (const el of matches) { let value = ""; try { value = rule.srcAttribute.toLowerCase() === "innertext" ? el.innerText : el.getAttribute(rule.srcAttribute); } catch (e) { value = e.message || "codeError"; } if (value || value === 0) { valuesByRuleId[rule.ruleId] = valuesByRuleId[rule.ruleId] || []; valuesByRuleId[rule.ruleId].push(value); // the scraped text/attribute value } } } return valuesByRuleId; // -> sent via the ReportRuleData message}- anonymous.browse.live
Receives per-page rule-match reports and full telemetry batches. Operated by Browse.live (Blur), the extension's own vendor, not an unrelated third party.
We observed a rule ID and match count leave the browser, not literal field text. The same rule format can also read a field's value; that path exists in code but was not switched on during our test.
Full page URL and referrer sent to Browse.live on every site you visit
Loading any webpage triggers an unprompted report to the extension's own server with the full page URL, referrer, browser version, and locale.
We confirmed this live on an ordinary Amazon.com visit unrelated to ad-blocking.
- Severity
- Medium unwanted
- Type
- Unexpected
- CWE
- CWE-359
- Source
- Dynamic sandbox
You navigate to any ordinary website, for example amazon.com.
The extension immediately reports the full page URL to its own telemetry server.
The report also includes the page's referrer, your browser's version, and locale.
- Page URLhttps://www.amazon.com/gp/cart/view.html
The exact address of every page you visit, including query parameters that can carry search terms or account details.
- Referrer URLhttps://www.google.com/search?q=best+laptop+deals
The page you came from, revealing your browsing path.
- Browser identity stringMozilla/5.0 (Windows NT 10.0; Win64; x64) Chrome/128.0.0.0
Your exact browser version and OS, which combined with other data can help fingerprint your device.
- Language / localeen-US
Your browser's language setting.
[ { "TelemetryReason": "Browse", "PageUrl": "https://www.amazon.com/", "RootUrl": "https://www.amazon.com/", "IsFrame": false }]The consent check that never blocks sending
// Fires on every top-frame navigation (background.js message handler// for "ContentInjected", called from content.js on every visible page)static async reportBrowseEvent(msg, sender) { const reason = "Browse"; if (!await shouldSampleThisReason(reason)) return; // sampling rate, not a consent gate const record = { TelemetryReason: reason, PageUrl: sender.url, RootUrl: sender.tab?.url, ReferrerUrl: msg.referrerUrl, IsFrame: sender.frameId !== 0, }; queueTelemetry(record); // merged with device/browser fields, then POSTed}// This function exists and checks the user's local "send telemetry" flag...static async isTelemetryEnabledInLocalConfig() { const cfg = await getLocalConfig(); return "sendTelemetry" in cfg && cfg.sendTelemetry === true;}// ...but nothing in the codebase calls it. The function that IS called// before every send ignores the flag entirely:class ConsentGate { static async canSendTelemetry() { return true; // hardcoded -- no config, no user action, ever blocks this }}static async sendBatchTelemetry(batch) { if (!await ConsentGate.canSendTelemetry()) return "notconsented"; // never hit return await httpPost(telemetryBatchUrl(), batch);}- anonymous.browse.live
Receives a report of every page you visit, including pages the ad-blocking feature never touches. Operated by Browse.live (Blur), the extension's own vendor.
Your full list of installed extensions is uploaded to Browse.live
On install, and again roughly every two hours, the extension asks Chrome for every extension you have installed and uploads each one's ID, version, and permissions to Browse.live's server.
- Severity
- Medium unwanted
- Type
- Unexpected
- CWE
- CWE-359
- Source
- Dynamic sandbox
You install the extension, or two hours pass while it keeps running.
It asks Chrome for your complete list of installed extensions and uploads it to Browse.live.
Each entry includes that extension's ID, version, and requested permissions.
- Extension IDghjakbehlhkjdiehaeffedomkakcenjo
This ID can be looked up in the Chrome Web Store to see exactly which extension you have installed, including security or password tools.
- Version number2.25.1729.2103
Which release of that extension you're running.
- Requested permissions["tabs","storage","webRequest","management"]
What that other extension is allowed to do, for example read every page you visit.
- Description textBrowser Extension that Hides Unfair Ads and Unsafe Search Results
That extension's store listing description.
{ "extIdVersionList": [ { "id": "ghjakbehlhkjdiehaeffedomkakcenjo", "version": "2.25.1729.2103", "description": "Browser Extension that Hides Unfair Ads and Unsafe Search Results", "permissions": [ "alarms", "declarativeNetRequest", "downloads", "management", "scripting", "storage", "tabs", "webRequest" ] } ]}Enumerate, then upload, every installed extension
// Runs on install, and again whenever more than 72e5 ms (2 hours)// have passed since the last check.static async runExtensionCheckIfDue() { const last = await getLastExtensionCheckTime(); if (isStale(last, /* 2 hours */ 7_200_000)) { await enumerateAndUploadInstalledExtensions(); }}static async enumerateAndUploadInstalledExtensions() { const priorVerdicts = await getCachedExtensionVerdicts(); let installed = await chrome.management.getAll(); installed = installed.filter(e => e.type === "extension"); // drops apps/themes only // merge in any cached verdict/prediction from a prior check, then: await uploadInstalledExtensions(installed); // -> POST /browse/extension/verify}static async uploadInstalledExtensions(extensions = []) { const lastChecked = await getLastExtensionCheckTime(); const nonce = await getOrCreateNonce(); const response = await verifyExtensionsOnServer(extensions, lastChecked, nonce); // server's verdicts are cached locally per extension ID for next time}// The actual network call:static async verifyExtensionsOnServer(extensions = [], lastVerified = "", nonce = "") { const url = `${verifyExtensionsUrl()}?lastVerified=${lastVerified}&nonce=${nonce}`; const body = { extIdVersionList: [...extensions] }; // every installed extension, full objects return await httpPost(url, body);}- anonymous.browse.live
Receives your complete browser extension inventory, including third-party extensions unrelated to Browse.live.
What it can do
Permissions this extension asks for, as declared in version 2.25.1729.2103. 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
*://*/*
See the address and title of every tab you have open
tabs
Store data in your browser
storage
Watch every request your browser makes
webRequest
Schedule its own background tasks
alarms
Run its own code inside the pages you visit
scripting
Start, monitor and manage your downloads
downloads
See, disable and uninstall your other extensions, including your security ones
management
Block and redirect the requests your browser makes
declarativeNetRequest
Where it sends data
Destinations our analysis observed Browse.live Web Safety 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.
- anonymous.browse.live
Browse.live Web Safety sends data to anonymous.browse.live. 3 other extensions we have analysed send data here.