Is Ad Blocker safe?

High risk

Blocks unwanted ads to provide a cleaner, faster browsing experience.

This extension removes intrusive advertisements such as banners from websites. It uses regularly updated filter lists and provides simple controls with minimal setup.

Ad-blocker.cov2.0.1Chrome Web Store
78Risk

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

Publishers can request a review.

Findings

Ad-blocker.co can remotely replace this extension's network-blocking rules

Every hour, this extension fetches a ruleset from ad-blocker.co, giving it full control over blocked/allowed sites.

The operator can push changes, incl. allowing blocked ad networks or blocking legitimate sites, without an update.

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

The extension's hourly alarm fires in the background.

No user interaction is required; this happens without user notification 60 minutes after install, then every hour thereafter.

The extension did this

The remote server replaces every dynamic network-blocking rule the extension is using.

The server decides which URLs the extension will block or allow from that point forward, until the next hourly poll.

When this fires

Every 1 hour

Chrome alarm 'updateNetRulesAlarm' created with periodInMinutes:60 and delayInMinutes:60. Triggers the two-stage fetch-and-inject cycle every hour.

Captured request
GEThttps://ad-blocker.co/directives/filters/?Hcrvj=ai9WZkg2SXJUU20veDY0&crx=enpannaocdooaplolobjfclkjefbeocn&bcounter=1&scounter=0&ucounter=0

JSON: { "GgZtxR": "/directives/filters/list/", "ZapY": 42 }; the server returns the path of the second fetch, which delivers YHbzkld (the rule array).

The code that does this

Two-stage remote rule injection in net_ff.js

Readable version
// Stage 1: index fetch resolves to a server-chosen pathasync function fetchAndApplyRuleset(entry) { const indexUrl = "https://ad-blocker.co" + entry.GgZtxR; // e.g. /directives/filters/list/ const cacheKey = `net_item_ZapY_${indexUrl}`; const cached = await chrome.storage.local.get([cacheKey]); if (cached[cacheKey] !== undefined && cached[cacheKey] >= entry.ZapY) { return; // already up-to-date } // Stage 2: fetch the actual ruleset JSON from the server-chosen path const rulesetUrl = await buildTelemetryUrl(indexUrl); const response = await fetch(rulesetUrl); const data = await response.json(); // Remove ALL current dynamic rules and replace with server-provided array const serverRules = data.YHbzkld; // array of declarativeNetRequest rule objects const currentRuleIds = (await chrome.declarativeNetRequest.getDynamicRules()).map(r => r.id); await chrome.declarativeNetRequest.updateDynamicRules({ removeRuleIds: currentRuleIds, // wipe every existing dynamic rule addRules: serverRules // install whatever the server sent }); // Cache the version to avoid redundant refetches data[cacheKey] = entry.ZapY; await chrome.storage.local.set(data);}
Server with full rule-replacement authority
    • ad-blocker.co

    Operator server for the two-stage ruleset update: returns the ruleset URL in stage 1, serves the declarativeNetRequest array (YHbzkld) in stage 2. Developer controls this domain.

Google search visits counted and sent to ad-blocker.co on every filter update

Visiting google.com/search increments a counter.

That count, plus how often the extension has blocked something, is sent hourly to the operator with a routine filter-update, revealing how often you use Google Search versus other sites.

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

You navigate to any Google Search results page.

The content script runs at document_start on every page load (*://*/*) and checks if the current URL matches google.*/search.

The extension did this

The extension records the visit and reports the running total to ad-blocker.co on the next hourly update.

The sCounter stored in chrome.storage.local is incremented immediately, then transmitted as &scounter=N in every subsequent filter-update request.

The code that does this

Search-detection IIFE in ad_no_main.js (content script, world=ISOLATED)

Readable version
(async () => { // Runs on every page load (matches: *://*/*) const isGoogleSearch = (location.hostname.startsWith('google.') || location.hostname.startsWith('www.google.')) && location.pathname === '/search'; if (isGoogleSearch) { // Increment the Google-search visit counter in the service worker chrome.runtime.sendMessage({ count: 1, message: 'sCounterMsg' }); }})();
The code that does this

URL builder $() in net_ff.js appends scounter to every filter-update request

Readable version
// Called before every fetch to ad-blocker.coasync function buildTelemetryUrl(baseUrl) { const cohortId = await getCohortId(); // Hcrvj — per-install persistent ID const { bCounter, sCounter, MCP } = await chrome.storage.local.get({ MCP: 0, sCounter: 0, // cumulative Google Search visits bCounter: 0 // cumulative blocked-resource events }); const hoursSinceInstall = MCP ? parseInt(Date.now() - MCP / 3.6e6) : 0; baseUrl += (baseUrl.includes('?') ? '&' : '?') + 'Hcrvj=' + cohortId; baseUrl += '&crx=' + chrome.runtime.id; baseUrl += '&bcounter=' + bCounter; baseUrl += '&scounter=' + sCounter; // Google Search visit count transmitted here baseUrl += '&ucounter=' + hoursSinceInstall; return baseUrl;}
Captured request
GEThttps://ad-blocker.co/directives/filters/?Hcrvj=ai9WZkg2SXJUU20veDY0&crx=enpannaocdooaplolobjfclkjefbeocn&bcounter=1&scounter=1&ucounter=0

JSON filter-index response (200 OK). The scounter=1 value reflects one Google Search visit recorded in the dynamic analysis session.

Query parameters transmitted in every filter-update request
  • Per-install cohort ID
    Hcrvj=ai9WZkg2SXJUU20veDY0

    A permanent identifier assigned by the server on first install. Ties every request from your browser to the same identity across sessions.

  • Google Search visit count
    scounter=14

    Cumulative number of Google Search pages loaded since the extension was installed. Lets the server measure how often you use Google Search.

  • Blocked-resource count
    bcounter=382

    Number of network requests the extension has blocked across all browsing. Approximates your overall browsing volume.

  • Extension runtime ID
    crx=enpannaocdooaplolobjfclkjefbeocn

    Chrome's internal ID for this extension instance. Combined with the cohort ID, this further narrows attribution.

Destination receiving search frequency data
    • ad-blocker.co

    Operator-controlled server. Receives the Google Search visit count (scounter) and block count (bcounter) with the permanent per-install cohort ID on every hourly filter update.

Permanent per-install tracking ID fetched from ad-blocker.co on first run

The first run, this extension contacts ad-blocker.co for a unique identifier (Hcrvj), stored permanently.

Every later request, including hourly filter updates, carries this ID, letting the operator track your usage across sessions.

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

You install the extension.

No browsing is needed; the service worker runs L() immediately on the chrome.runtime.onInstalled event.

The extension did this

The extension fetches a permanent tracking ID from ad-blocker.co and links it to every future request your browser makes through this extension.

The Hcrvj value is stored in chrome.storage.local and appended to all outbound requests for the lifetime of the install, with no notification to the user.

The code that does this

Cohort-ID assignment in service_worker.js, function L()

Readable version
async function getCohortId() { // Read cached ID from persistent storage let { Hcrvj: cohortId } = await chrome.storage.local.get({ Hcrvj: null }); if (!cohortId) { // First run — request a new ID from the server try { const response = await fetch( `https://ad-blocker.co/cid/?crx=${chrome.runtime.id}` ); const data = await response.json(); cohortId = data.Hcrvj || 'default'; } catch { cohortId = 'default'; } // Persist — survives browser restarts and extension updates await chrome.storage.local.set({ Hcrvj: cohortId }); } return cohortId;}
Captured request
GEThttps://ad-blocker.co/cid/?crx=enpannaocdooaplolobjfclkjefbeocn

JSON: { "Hcrvj": "ai9WZkg2SXJUU20veDY0..." }, the server-assigned cohort ID returned on first install.

What's stored on your device

The server-assigned cohort ID stored on first install. Persists indefinitely, prepended to every outbound request to ad-blocker.co.

Location
chrome.storage.local key 'Hcrvj'
Contents
{  "Hcrvj": "ai9WZkg2SXJUU20veDY0AQIDBA"}
Server that issues and receives the cohort ID
    • ad-blocker.co

    Issues the Hcrvj cohort ID on first install via /cid/. Gets the same ID back as a query parameter on every later filter-update and ruleset request, enabling tracking.

What it can do

Permissions this extension asks for, as declared in version 2.0.1. 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>

  • Schedule its own background tasks

    alarms

  • Store data in your browser

    storage

  • Store an unlimited amount of data in your browser

    unlimitedStorage

  • Block and redirect the requests your browser makes

    declarativeNetRequest

  • Run its own code inside the pages you visit

    scripting

  • Watch every request your browser makes

    webRequest

Updated 30 September 2026enpannaocdooaplolobjfclkjefbeocn