Is Popup Blocker - Blocks annoying Popups safe?
Popup Blocker fetches per-domain JavaScript from popupsblocker.org and runs it in the main page context of any site you visit.
The extension's service worker pulls a configuration from popupsblocker.org on install and every 24 hours, then stores per-domain JavaScript strings that its content script injects into the main world of matching pages via chrome.scripting.executeScript. Because the server can replace those stored strings (and the extension's entire network-filtering ruleset) at any time, the code that runs on your pages is fully server-controlled. It also assigns a persistent server-issued user ID and uploads your sync-storage counters (total page loads plus Google, Bing, and Yahoo search counts) to the same server on each update cycle.
Who publishes itPopup Blocker - no other listings under this identity
Popup Blocker - no other listings under this identity
What this publisher told the store about itself, and the other listings that told it the same thing.
AI-generated. Findings may contain errors. Those marked Verified have been manually reviewed.
Publishers can request a review.
Findings
Server-Controlled JavaScript Injected Into All Visited Pages
Popup Blocker fetches JS strings from popupsblocker.org and stores them locally.
Each visit sends your domain to the service worker, which injects the matching script into the MAIN world.
Testing found 32 domain scripts run on first visit.
- Severity
- Critical unwanted
- Type
- Unexpected
- CWE
- CWE-829
- Source
- Dynamic sandbox
You navigate to any web page, any site.
The extension injects JavaScript into the page's MAIN execution context using a script fetched from its server at install time.
The injected code runs in the same environment as the page itself, with access to the page's DOM, forms, and JavaScript globals.
JavaScript keyed by domain, loaded from the server. Runs on that site when visited, and can be swapped by the server at any update.
- Location
- chrome.storage.local (populated by server response at install)
{ "google": "(() => { const nuke = () => { document.querySelectorAll('#tads, .uEierd, .commercial-unit-desktop-top, .commercial-unit-desktop-right, .ads-ad, [aria-label=\"Ads\"]').forEach(el => el.style.display = 'none'); }; setInterval(nuke, 500); })();", "default": "console.log('[AdBlock] Standard protection enabled.');", "syncuid": "a7f3b9c2-1e4d-4f8a-b3c1-9e2d5f7a3b1c", "twitter": "(() => { const nuke = () => { document.querySelectorAll('[data-testid=\"tweet\"]').forEach(tweet => { if (tweet.innerText.includes('Promoted')) { const cell = tweet.closest('[data-testid=\"cellInnerDiv\"]'); if(cell) cell.style.display = 'none'; } }); document.querySelectorAll('[data-testid=\"sidebarColumn\"] [data-testid=\"trend\"]').forEach(t => { if(t.innerText.includes('Promoted')) t.style.display='none'; }); }; setInterval(nuke, 1000); })();", "facebook": "(() => { const nuke = () => { document.querySelectorAll('div[aria-label=\"Sponsored\"]').forEach(el => { const card = el.closest('[role=\"feed\"] > div, [data-pagelet^=\"FeedUnit_\"]'); if (card) card.style.display = 'none'; }); document.querySelectorAll('iframe[src*=\"facebook.com/plugins/like.php\"]').forEach(el => el.remove()); }; setInterval(nuke, 1000); })();", "instagram": "(() => { const nuke = () => { document.querySelectorAll('article').forEach(article => { const label = article.querySelector('span'); if (label && (label.innerText === 'Sponsored' || label.innerText === 'Paid partnership')) article.style.display = 'none'; }); }; setInterval(nuke, 1000); })();"}JSON object with 32 domain-keyed JS script strings plus a syncuid. The extension stores all values directly to chrome.storage.local and marks the unique user ID in both sync and local storage.
The injection path from content script to MAIN world execution.
content_script.js — fires on every page at document_start (already readable)
// Runs before the page's own scripts execute.// Sends the current hostname to the service worker.// Works in all frames (iframes too), not just the top-level page.chrome.runtime.sendMessage({ action: "CHECK_RULES", domain: window.location.hostname, // e.g. 'google.com' isTop: window === window.top // true if this is the main frame});serviceworker.js — handleRuleCheck() injects the server-supplied code
// ruleContent is a JS string fetched from chrome.storage.local.// It was originally loaded from the server on install and can be// replaced by a subsequent server sync (every 24 hours).//// world: 'MAIN' means the code runs in the same JS context as// the page itself — it can read passwords, intercept form submits,// access page-level variables, etc.chrome.scripting.executeScript({ target: { tabId: tabId, frameIds: [frameId] }, func: secureApply, // inlines ruleContent as a <script> tag args: [ruleContent], // the JS string from storage world: 'MAIN', // full page environment access injectImmediately: true // runs before page scripts});Chrome's `scripting` API offers two execution worlds: `ISOLATED` (the default) and `MAIN`. Code in the `ISOLATED` world runs in a sandboxed context with access to the DOM but not to the page's own JavaScript variables and functions. Code injected into the `MAIN` world shares the same JavaScript environment as the page — meaning it can read form field values, intercept authentication tokens held in memory, hook into page-level event handlers, and interact with any JavaScript library loaded by the site.
For an ad blocker, `MAIN` world access is not required — DOM element hiding can be done from `ISOLATED` world. The choice to use `MAIN` world injection means the server-supplied code has the broadest possible access to each page you visit.
- popupsblocker.org
Primary server. Supplies JS strings run in the MAIN world on each navigation and controls declarativeNetRequest rules. Sends a unique syncuid on first contact.
Run this in Chrome DevTools while the extension is active. It hooks chrome.scripting.executeScript in the service worker context to log each time the extension injects code into the MAIN world, printing the target tab, domain key, and the code string about to be executed.
- Chrome with Developer mode enabled
// popup-blocker-main-world-detector.js// Paste into the Popup Blocker service worker DevTools console.// (chrome://extensions → enable Developer mode → click the service// worker link for Popup Blocker)(function() { const orig = chrome.scripting.executeScript.bind(chrome.scripting); chrome.scripting.executeScript = function(params, ...rest) { if (params && params.world === 'MAIN') { const code = params.args && params.args[0]; console.group('[POPUP_BLOCKER_DETECT] MAIN world injection'); console.log('Target tab:', params.target?.tabId); console.log('Frame IDs:', params.target?.frameIds); console.log('Code to be injected (first 500 chars):', String(code).slice(0, 500)); console.groupEnd(); } return orig(params, ...rest); }; console.log('[POPUP_BLOCKER_DETECT] Monitoring chrome.scripting.executeScript for MAIN world injections...');})();- 1Open chrome://extensions, enable Developer mode.
- 2Open Popup Blocker's service worker DevTools.
- 3Paste this script.
- 4Visit google.com or a site in the extension's domain list.
- 5Watch for [POPUP_BLOCKER_DETECT] entries.
Server Replaces Browser Network Rules at Runtime
On install, update, or daily sync, Popup Blocker asks popupsblocker.org for its rule set.
A 'rules' key replaces all filtering rules immediately, no user action.
Testing captured 4,472 redirect and 29 block rules via updateDynamicRules.
- Severity
- High unwanted
- Type
- Unexpected
- CWE
- CWE-829
- Source
- Dynamic sandbox
The extension installs, updates, or its 24-hour alarm fires.
No user action is required; this happens automatically in the background.
The server replaces all of the extension's active network filtering rules with a new set of its own choosing.
The new rules take effect immediately for all subsequent browser network requests.
JSON response contained a 'rules' array with 4,472 redirect rules (routing matched domains to the extension's close.html page) and 29 block rules. Also included per-domain JavaScript strings and a syncuid for the new install. Total response was approximately 1,397 characters of rule data.
The remote rule replacement, shipped source vs annotated version.
Rule replacement — annotated
// If the server response contains a 'rules' key:if (json.rules) { // Parse the rules (server may send them as a JSON string or an array) const newRules = typeof json.rules === 'string' ? JSON.parse(json.rules) : json.rules; // Remove ALL currently active dynamic rules const currentRules = await chrome.declarativeNetRequest.getDynamicRules(); const removeRuleIds = currentRules.map(rule => rule.id); // collect every active rule ID // Atomically: delete old rules, install server-provided rules await chrome.declarativeNetRequest.updateDynamicRules({ removeRuleIds: removeRuleIds, // wipe slate clean addRules: newRules // server now controls what is blocked/redirected }); // From this point on, the browser's network filter for this extension // matches exactly what the server sent — nothing more, nothing less.}URL construction — all sync storage is sent as query parameters
// params is the full chrome.storage.sync contents on update requests.// This means counters (cnt_total, cnt_google, cnt_bing, cnt_yahoo),// the syncuid, and lastUpdated are all serialized into the URL.const url = `https://popupsblocker.org/jsonhybrid17-dev.php` + `?c=${type}` + // 'uid' on install, 'update' on refresh `&${new URLSearchParams(params)}` + // full sync storage contents `&version=${manifestVersion}` + // extension version `&extid=${chrome.runtime.id}`; // extension IDdeclarativeNetRequest (DNR) rules are enforced at the browser's network layer — they intercept HTTP/HTTPS requests before any web page code sees them. A server-controlled DNR rule set can:
- **Redirect** requests for any domain to any URL the rule author chooses (including the extension's own pages) - **Block** requests to any domain without notifying the user or the page - **Modify headers** on requests or responses
The 4,472-domain redirect rule set observed in our analysis routes matching domains to the extension's `close.html` page. The 29-domain block list drops requests without a prompt. Because the server can replace these rules at any 24-hour sync, the full set of blocked and redirected domains can change without any reinstall or user notification.
- popupsblocker.org
Primary server. Supplies declarativeNetRequest rules for the network layer, plus per-domain JS strings, and assigns a syncuid. Contacted on install, update, and every 24 hours.
Persistent server-assigned user ID tracks you across sessions
On install, the extension gets a unique 72-char ID from its server, stored in Chrome sync and local storage.
Sent with every daily check-in, letting the server link usage across devices.
Also embedded in the uninstall URL.
- Severity
- Medium unwanted
- Type
- Unexpected
- CWE
- CWE-359
- Source
- Dynamic sandbox
You install the extension for the first time.
No interaction needed, the UID registration happens automatically during the install lifecycle.
The extension requests a permanent tracking identifier from its server and stores it in your browser.
The ID persists in chrome.storage.sync, so it survives extension reinstalls and follows you across every signed-in Chrome profile.
JSON object containing syncuid: 'sfVA1xqoJIi...' (72-char server-assigned ID) plus optional rule updates.
- Permanent user IDsfVA1xqoJIibKm8nPqRx2dLwTcYeUoAzHgFvNsBjMkWlXtDyEpCrQu0v3i7h4O
A 72-character token assigned by the server that uniquely identifies your browser installation across all future sessions.
- Total page visitscnt_total: 1482
Running count of every web page you load while the extension is active, sent with each daily update.
- Search-engine usagecnt_google: 204, cnt_bing: 11, cnt_yahoo: 3
Separate counters for Google, Bing, and Yahoo searches, all tied to your permanent ID.
- Extension version + runtime IDversion=11750&extid=dhemafmfialpibmahglbfgjihhfheepp
Extension version and Chrome's internal runtime ID are appended to every outbound request.
UID registration and storage, serviceworker.js
// serviceworker.js:332-393 (abridged)if (!syncuid) { logger.log('Registering new user ID...'); await syncRemoteConfiguration('uid', '');}// Inside syncRemoteConfiguration, after server JSON is parsed:if (key === 'syncuid') { return Promise.all([ chrome.storage.sync.set({ [key]: value }), // survives reinstall via account sync chrome.storage.local.set({ [key]: value }) // local copy for fast reads ]);}Uninstall beacon includes full sync storage, serviceworker.js:410-419
async function setUninstallUrl() { const dataSync = await chrome.storage.sync.get(); // ALL sync keys const dataLocal = await chrome.storage.local.get(['syncuid']); if (dataLocal['syncuid'] && !dataSync['syncuid']) { dataSync['syncuid'] = dataLocal['syncuid']; } const searchParams = new URLSearchParams(dataSync); // includes syncuid, cnt_total, cnt_google, … await chrome.runtime.setUninstallURL( `https://popupsblocker.org/uninstall.php?${searchParams.toString()}` );}- popupsblocker.org
Developer server. Receives UID assignment (?c=uid), daily check-ins (?c=update) with usage metrics, and an uninstall beacon (/uninstall.php) with full sync-storage contents.
Where it sends data
Destinations our analysis observed Popup Blocker 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.
- popupsblocker.org
Popup Blocker sends data to popupsblocker.org. No other extension we have analysed sends data here.