Is Ad Blocker Pro - stop invasive ads safe?

High risk

Ad Blocker Pro is high risk. The extension polls a server every 24 hours and on install; a 'rules' key in the response replaces its ruleset with no version check, signature, or consent. A capture showed rules redirecting 4,600+ domains and blocking 1,485 more.…

AdBlock Inc.v2026.9.14.12Chrome Web Store
76Risk
Who publishes it

AdBlock Inc. - no other listings under this identity

What this publisher told the store about itself, and the other listings that told it the same thing.

Publisher
AdBlock Inc.

AI-generated. Findings may contain errors. Those marked Verified have been manually reviewed.

Publishers can request a review.

Findings

Remote server replaces all blocking rules on every visited site

The extension polls a server every 24 hours and on install; a 'rules' key in the response replaces its ruleset with no version check, signature, or consent.

A capture showed rules redirecting 4,600+ domains and blocking 1,485 more.

Severity
High unwanted
Type
Unexpected
CWE
CWE-829
Source
Dynamic sandbox
What actually happens
You did this

The extension's daily alarm fires (or install/update triggers an immediate sync).

syncConfiguration() posts extension ID, version, and storage state to ad-blocker-pro.org/adbpro-202602.php.

The extension did this

If the server response JSON contains a 'rules' key, all existing declarativeNetRequest dynamic rules are removed and replaced with the server-supplied ruleset.

updateNetworkFirewall() is called with unvalidated server data and applies rules across every tab via <all_urls>.

Captured request
POSThttps://ad-blocker-pro.org/adbpro-202602.php

HTTP 200, ~167 KB JSON. Response contained a 'rules' key with 2 declarativeNetRequest rules: Rule 300 (priority 1, redirect action, regexFilter='^http.+', 4,612 requestDomains targeting extension's close.html) and Rule 301 (priority 1, block action, 1,485 requestDomains including google-analytics.com). Captured live during dynamic analysis on 2026-04-25.

Headers
Content-Type
application/x-www-form-urlencoded
Body
syncuid=&ads_blocked=42&extid=ifidkgmkpihooaknfaapgkejcgcbniek&version=20260206012&c=update
The code that does this

Rule replacement logic, sw.js lines 239-293

Readable version
// sw.js lines 239-245if (json.rules) {    let safeRules = json.rules;    if (typeof safeRules === 'string') {        try { safeRules = JSON.parse(safeRules); } catch (e) { safeRules = []; }    }    const currentAllowlist = json.allowlist || (await chrome.storage.sync.get('allowlist')).allowlist;    await updateNetworkFirewall(safeRules, currentAllowlist);}// sw.js lines 271-295async function updateNetworkFirewall(blockingRules, allowlist = []) {    try {        const currentRules = await chrome.declarativeNetRequest.getDynamicRules();        const removeRuleIds = currentRules.map(r => r.id);                const allowRules = (allowlist || []).map((host, index) => ({            id: 5000 + index,             priority: 999,             action: { type: "allow" },            condition: {                 urlFilter: `||${host}`,                 resourceTypes: ["main_frame", "sub_frame", "script", "image", "xmlhttprequest"]             }        }));        const finalRules = [...(blockingRules || []), ...allowRules];        await chrome.declarativeNetRequest.updateDynamicRules({            removeRuleIds: removeRuleIds,            addRules: finalRules        });    } catch (e) {        console.error("Firewall update failed:", e);    }}
Remote control endpoint
    • ad-blocker-pro.org

    Receives telemetry (ID, version, ad-block count, sync storage) and returns the ruleset to enforce. Operator-controlled; no public ownership disclosure.

When this fires

Every 1 day

Service worker syncs with the remote server every 24 hours via a chrome.alarms periodic alarm, and immediately on install and on extension update.

Check if you're affected

Fetches the current declarativeNetRequest dynamic rules installed by the extension and prints them to the console. Run in the browser's extension service worker DevTools console to see what ruleset the server has pushed.

Requires
  • Chrome browser with the extension installed
  • Developer mode enabled in chrome://extensions
check-dnr-rules.js · js
// Run in the extension's service worker DevTools console (chrome://extensions → inspect views: service worker)// Lists all currently active dynamic declarativeNetRequest rules set by the extension.(async () => {  const rules = await chrome.declarativeNetRequest.getDynamicRules();  console.log(`Total dynamic rules: ${rules.length}`);  rules.forEach((r, i) => {    console.log(`\nRule ${i+1} (id=${r.id}, priority=${r.priority})`);    console.log('  action:', JSON.stringify(r.action));    if (r.condition.requestDomains) {      console.log(`  requestDomains: [${r.condition.requestDomains.length} entries] — first 5:`, r.condition.requestDomains.slice(0,5));    }    if (r.condition.regexFilter) {      console.log('  regexFilter:', r.condition.regexFilter);    }    if (r.condition.urlFilter) {      console.log('  urlFilter:', r.condition.urlFilter);    }  });})();
How to run it
  1. 1Open chrome://extensions, enable Developer mode.
  2. 2Click 'inspect views: service worker'.
  3. 3Paste this script into the Console, press Enter.
  4. 4Compare printed rules against bundled rules.json for server-pushed changes.

Browser storage and usage data sent to ad-blocker-pro.org on every sync

Installing, updating, or leaving Ad Blocker Pro running a day sends your sync storage, sites with ad-blocking disabled, plus ad-block count, ID, version, to ad-blocker-pro.org.

A tracking token from first sync is resent each later request.

Severity
Medium unwanted
Type
Unexpected
CWE
CWE-359
Source
Dynamic sandbox
What actually happens
You did this

You install Ad Blocker Pro, update it to a new version, or simply leave it running for a day.

A background alarm re-runs the same sync routine every 24 hours whether or not you've touched the extension.

The extension did this

The extension reads your entire browser sync storage and blocked-ad counter, then sends them to ad-blocker-pro.org along with your extension ID and installed version.

The server responds with a persistent tracking token that the extension stores and resends on every future sync.

What's included in every sync request
  • Ad-block sync ID
    sfVA0RasI4eShnIXpjtd2LeSO5OSfgs+bSuRHFZHECW1C58CZYC/8ryBPpTkO8ad0KVjAAGgQ+hIAQ==

    A tracking token the server issues on first connection and the extension stores locally, so every later request carries the same identifier.

  • Sites you've allowed ads on
    docs.google.com, drive.google.com, gemini.google.com, chatgpt.com, slack.com, office.com, microsoft365.com, youtube.com, netflix.com, github.com, stackoverflow.com

    Every domain in your personal ad-block exception list is sent in full, showing which sites you added an exception for.

  • Ads blocked count
    5

    Your cumulative ad-block counter, which tracks how actively you use the extension.

  • Extension identifier and version
    ojphgkmddbkcgbfdndpabcnfcgmfppdk, version 2026020612

    Chrome's unique ID for this specific install, plus the installed extension version.

Captured request
POSThttps://ad-blocker-pro.org/adbpro-202602.php

HTTP 200. Captured in full during dynamic analysis (407-byte body).

Headers
Content-Type
application/x-www-form-urlencoded
Body
allowlist=docs.google.com,drive.google.com,gemini.google.com,chatgpt.com,slack.com,office.com,microsoft365.com,youtube.com,netflix.com,github.com,stackoverflow.com&lastUpdated=1786890239006&syncnext=43200000&syncuid=sfVA0RasI4eShnIXpjtd2LeSO5OSfgs+bSuRHFZHECW1C58CZYC/8ryBPpTkO8ad0KVjAAGgQ+hIAQ==&ads_blocked=5&extid=ojphgkmddbkcgbfdndpabcnfcgmfppdk&version=2026020612&c=update
The code that does this

syncConfiguration() and its triggers, sw.js

Readable version

syncConfiguration() — reads storage, builds the POST, applies the response

sw.js
async function syncConfiguration(isInstall = false) {    console.group("🔄 [AdBlock Sync] Request Initiated");    const now = Date.now();    if (now - lastApiCallTime < 10000) {        console.warn("⏳ Sync skipped: Concurrency limit (10s cooldown).");        console.groupEnd();        return;    }    if (!isInstall) {        console.log("⏳ Waiting 5 seconds before API call...");        await new Promise(resolve => setTimeout(resolve, 5000));                if (Date.now() - lastApiCallTime < 10000) return;    }    lastApiCallTime = Date.now();    try {        const [syncData, localData] = await Promise.all([            chrome.storage.sync.get(),            chrome.storage.local.get('adsBlocked')        ]);                const adsBlockedCount = localData.adsBlocked || 0;        const postData = new URLSearchParams({            ...syncData,            ads_blocked: adsBlockedCount,            extid: chrome.runtime.id,            version: chrome.runtime.getManifest().version.replaceAll('.', ''),            c: 'update'        });        console.log("📡 API URL:", CONFIG.apiUrl);        console.log("📤 POST Payload:", postData.toString());        const response = await fetch(CONFIG.apiUrl, {            method: 'POST',            headers: { 'Content-Type': 'application/x-www-form-urlencoded' },            body: postData        });        if (!response.ok) {            console.error("❌ API HTTP Error:", response.status);            console.groupEnd();            return;        }        const rawText = await response.text();        let json;        try {            json = JSON.parse(rawText);            console.log("📦 API Response JSON:", json);        } catch (parseError) {            console.error("❌ CRITICAL: Valid JSON not found.");            console.log(rawText);            console.groupEnd();            return;        }        const newLocalData = {};        const syncUpdate = {};                if (json.rules) {            let safeRules = json.rules;            if (typeof safeRules === 'string') {                try { safeRules = JSON.parse(safeRules); } catch (e) { safeRules = []; }            }            const currentAllowlist = json.allowlist || (await chrome.storage.sync.get('allowlist')).allowlist;            await updateNetworkFirewall(safeRules, currentAllowlist);        }        for (const [key, value] of Object.entries(json)) {            if (key === 'rules') continue;             if (key === 'allowlist' || key.startsWith('sync')) {                syncUpdate[key] = value;            } else {                newLocalData[key] = value;            }        }        if (Object.keys(newLocalData).length > 0) await chrome.storage.local.set(newLocalData);        if (Object.keys(syncUpdate).length > 0) await chrome.storage.sync.set(syncUpdate);                await chrome.storage.sync.set({ lastUpdated: Date.now() });        updateUninstallHook();        console.log("✅ Sync Complete.");    } catch (e) {        console.error("❌ Sync Exception:", e);    }    console.groupEnd();}

Registered triggers — install, update, and the 24-hour alarm

sw.js
chrome.runtime.onInstalled.addListener(async (details) => {    chrome.alarms.create(CONFIG.alarmName, { periodInMinutes: 1440 });    if (details.reason === "install") {        console.log("🎉 Extension Installed. Loading defaults...");        try {            // Load Scripts Locally            const scriptsRes = await fetch(chrome.runtime.getURL('initial_scripts.json'));            const scriptsData = await scriptsRes.json();            const { allowlist, ...scriptBody } = scriptsData;            await chrome.storage.local.set(scriptBody);            await chrome.storage.sync.set({ allowlist });            // Load Rules Locally            const rulesRes = await fetch(chrome.runtime.getURL('rules.json'));            const rulesData = await rulesRes.json();            await updateNetworkFirewall(rulesData, allowlist);                        // Immediate Sync on Install            syncConfiguration(true);            updateUninstallHook();        } catch (e) {            console.error("Initial setup failed", e);        }    } else if (details.reason === "update") {        console.log("🚀 Extension Updated. Force Sync...");        // API Sync ONLY (Skip local defaults)        syncConfiguration(false);    }});
Where your data goes
    • ad-blocker-pro.org

    Receives your full sync storage, ad-block count, extension ID, and version on every install, update, and daily sync. Operator-controlled; no public ownership disclosure.

When this fires

Every 1 day

Runs immediately on install, again on every extension update, and then automatically every 24 hours via a background alarm for as long as the extension stays installed.

What it can do

Permissions this extension asks for, as declared in version 2026.02.06.12. Asking for a permission is not a finding on its own - it is what the extension can do if it chooses to. The current listed version is 2026.9.14.12, which we have not unpacked yet.

  • Read and change your data on every site you visit

    <all_urls>

  • Store data in your browser

    storage

  • Run its own code inside the pages you visit

    scripting

  • Block and redirect the requests your browser makes

    declarativeNetRequest

  • Schedule its own background tasks

    alarms

Updated 30 September 2026ifidkgmkpihooaknfaapgkejcgcbniek