Is Eclipse Ad Blocker safe?

Medium risk

Eclipse Ad Blocker is medium risk. Eclipse Ad Blocker fetches rules, whitelist, net filters, and CSS config from eclipseadblocker.com. The service worker applies these to blocking rules and page-context scriptlets, with no signature or hash check before applying updates.…

EclipsevunknownChrome Web Store
45Risk

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

Publishers can request a review.

Findings

SeverityMEDIUM
ClassUNWANTED
TypeUnexpected
CWECWE-829
SourceAI SANDBOX

Remote Eclipse Ad Blocker config changes page behavior

Eclipse Ad Blocker fetches rules, whitelist, net filters, and CSS config from eclipseadblocker.com.

The service worker applies these to blocking rules and page-context scriptlets, with no signature or hash check before applying updates.

01EvidenceCAUSE EFFECT
What actually happens
You did this

You keep Eclipse Ad Blocker installed long enough for its scheduled update check to run.

The extension did this

The extension fetches remote configuration and applies it to filtering rules, page CSS, and scriptlets.

The confirmed evidence did not show cryptographic verification before those updates were used.

02EvidenceTEMPORAL PATTERN
When this fires
Every 1 day

The extension refreshes remote configuration on a recurring schedule after installation.

03EvidenceNETWORK CAPTURE
Captured request
GEThttps://eclipseadblocker.com/rules/?userId=ai9WZkg2SXJUU20veDY0QTU5UmhKeUhYM0p2UU1NR082M21ua3ZvNW1SWWszR1JMYmlHY1ZYMUNOdm5rSW5jVnZ2b0taYStB
200 OK. Dynamic analysis observed the rules endpoint returning rules_*.json URLs, followed by rule JSON requests and scriptlet execution calls.
04EvidenceFIELD TABLE
Remote configuration fields consumed by the extension
FieldValueWhy it matters
Rule list URL
https://eclipseadblocker.com/rules/rules_1.json?userId=ai9WZkg2SXJUU20veDY0QTU5UmhKeUhYM0p2UU1NR082M21ua3ZvNW1SWWszR1JMYmlHY1ZYMUNOdm5rSW5jVnZ2b0taYStBThis tells your browser which additional rule file to download and store for page-specific behavior.
Remote version number
42This decides whether the extension treats the server response as newer than what is already stored.
Whitelist domains
example.comThese domains can be added to the list where the extension allows requests instead of blocking them.
Scriptlet function and arguments
json-prune with server-provided argumentsThese values choose which page-context scriptlet runs and what arguments it receives.
CSS selector list
popIn_infinite_adThese selectors decide which page elements the extension hides after a page loads.
05EvidenceCODE COMPARE
The code that does this

Remote update and scriptlet execution paths

What it actually does
Daily update gate and four remote configuration fetchesServiceWorker.js
async update() {
        const {updatedAt} = await chrome.storage.local.get(['updatedAt']);
        const updatePeriodMs = 24 * 3600000;
        if (updatedAt && (Date.now() - updatedAt) < updatePeriodMs) {
            this.log('Update was perfomed less then 24 hours ago');
            return;
        }
        
        await Promise.all([this.updateRules(), this.updateWhitelist(), this.updateNetFilters(), this.updateCommonCSS()]);
        await chrome.storage.local.set({updatedAt: Date.now()});
    }
Rule list, whitelist, net filter, and CSS responses are applied without a signature checkServiceWorker.js
async updateRulesData(url, version) {
        const storageKey = 'rule_' + url;
        const d = await this.download(url);
        d[storageKey] = version;
        await chrome.storage.local.set(d);
    }

    async updateRules() {
        const rulesList = await this.download(RULES_URL);
        const promises = [];

        for (const ruleInfo of rulesList) {
            const storageKey = 'rule_' + ruleInfo.url;
            const storageInfo = await chrome.storage.local.get([storageKey]);
            const storageVersion = storageInfo[storageKey];
            if (storageVersion && storageVersion >= ruleInfo.version) {
                this.log(ruleInfo.url + ' is updated.');
                continue;
            }

            promises.push(this.updateRulesData(ruleInfo.url, ruleInfo.version))
        }

        await Promise.all(promises);
    }

    async updateWhitelist() {
        const data = await this.download(WHITE_LIST_URL);

        const {localWhiteListVersion} = await chrome.storage.local.get({localWhiteListVersion: 0});
        if (localWhiteListVersion >= data.version) {
            this.log('whitelist is updated.');
            return;
        }

        const {whiteList} = await chrome.storage.local.get({whiteList: []});
        const newWhiteList = [...new Set([...whiteList ,...data.domains])];
        await chrome.storage.local.set({localWhiteListVersion: data.version, whiteList: newWhiteList});
        await this.updateDynamicNetRules();
    }

    async updateNetFilters() {
        const data = await this.download(NET_FILTERS_URL);

        const {localNetListVersion} = await chrome.storage.local.get({localNetListVersion: 0});
        if (localNetListVersion >= data.version) {
            this.log('Net filters are updated.');
            return;
        }

        const responseNetFilters = await fetch(data.url);
        const netFilters = await responseNetFilters.json();
        await chrome.storage.local.set({localNetListVersion: data.version, netFilters: netFilters});
        await this.updateDynamicNetRules();
    }   

    async updateCommonCSS() {
        const data = await this.download(COMMON_CSS_URL);

        const {localCommonCssVersion} = await chrome.storage.local.get({localCommonCssVersion: 0});
        if (localCommonCssVersion >= data.version) {
            this.log('Common CSS is updated.');
            return;
        }

        const commonCSS = await this.download(data.url);
        await chrome.storage.local.set({localCommonCssVersion: data.version, commonCSS: commonCSS});
    }
Stored rules can inject scriptlets and CSS into pagesServiceWorker.js
async runScriptlets(sender, scriptletsList, srcList) {

        const functionToInject = (script) => {
            const scriptTag = document.createElement('script');
            scriptTag.setAttribute('type', 'text/javascript');
            scriptTag.textContent = script;
            const parent = document.head || document.documentElement;
            parent.appendChild(scriptTag);
            if (scriptTag.parentNode) {
                scriptTag.parentNode.removeChild(scriptTag);
            }
        };

        if (scriptletsList) {
            for (const item of scriptletsList) {
                try {
                    let code = scriptlets.invoke({name:item.function, args: item.args})
                    let tabId = sender.tab.id;
                    await chrome.scripting.executeScript({
                        target: {tabId},
                        world: 'MAIN',
                        func: functionToInject,
                        args: [code],
                        injectImmediately: true
                    });
                } catch (e) {
                    console.log(e);
                }
            }
        }

        if (srcList) {
            for (const src of srcList) {
                try {
                    let tabId = sender.tab.id;
                    await chrome.scripting.executeScript({
                        target: {tabId},
                        world: 'MAIN',
                        func: functionToInject,
                        args: [src],
                        injectImmediately: true
                    });
                } catch (e) {
                    console.log(e);
                }
            }
        }
    }

    async insertCSS(sender, cssList, excludeCssList) {
        const {commonCSS} = await chrome.storage.local.get({commonCSS: []});
        let totalList = commonCSS;
        
        if (cssList) {
            totalList = [...totalList, ...cssList];
        }

        if (excludeCssList) {
            totalList = totalList.filter(item => excludeCssList.indexOf(item) === -1);
        }

        const chunkSize = 3000;
        for (let i = 0; i < totalList.length; i += chunkSize) {
            const chunk = totalList.slice(i, i + chunkSize);
            const css = chunk.join(', ') + '{ display: none!important; }';
            chrome.scripting.insertCSS({
                target: {tabId: sender.tab.id},
                css: css
            }).catch((error) => {this.log(error)});
        }
    }

    async blockAds(sender) {
        if (sender.frameId) {
            return null;
        }

        let domain = new URL(sender.tab.url).hostname;
        domain = domain.replace('www.', '').toLowerCase();
        if (await this.isInWhiteList(domain)) {
            return null;
        }

        const data = await chrome.storage.local.get([domain]);
        const rules = data[domain];
        if (!rules) {
            return null;
        }

        this.runScriptlets(sender, rules.scriptlets, rules.src);
        this.insertCSS(sender, rules.css, rules.exclude_css);

        return {extcss_list: rules.ext_css};
    }
06EvidenceTHIRD PARTY LIST
Remote host controlling update responses
  • eclipseadblocker.com

    Serves the rules, whitelist, net filter, common CSS, and rule JSON resources consumed by the extension.

SeverityMEDIUM
ClassUNWANTED
TypeUnexpected
CWECWE-359
SourceAI SANDBOX

Stable userId sent with Eclipse Ad Blocker updates

Eclipse Ad Blocker stores a stable userId in Chrome sync storage and attaches it to config requests to eclipseadblocker.com.

The same ID appears on whitelist, filter, CSS, and rules-update requests, tying them to one profile.

01EvidenceCAUSE EFFECT
What actually happens
You did this

You install Eclipse Ad Blocker and leave it enabled.

The extension did this

The extension stores a userId and attaches it to its configuration update requests.

The same value was observed on whitelist, net filter, common CSS, and rules requests.

02EvidenceNETWORK CAPTURE
Captured request
GEThttps://eclipseadblocker.com/rules/?userId=ai9WZkg2SXJUU20veDY0QTU5UmhKeUhYM0p2UU1NR082M21ua3ZvNW1SWWszR1JMYmlHY1ZYMUNOdm5rSW5jVnZ2b0taYStB
200 OK. Dynamic analysis observed the same userId value on the rules endpoint and on whitelist, net_filters, and common_css requests.
03EvidenceFIELD TABLE
Identifier and context sent in update URLs
FieldValueWhy it matters
Your extension userId
ai9WZkg2SXJUU20veDY0QTU5UmhKeUhYM0p2UU1NR082M21ua3ZvNW1SWWszR1JMYmlHY1ZYMUNOdm5rSW5jVnZ2b0taYStBThis value identifies the same browser profile across repeated update checks.
Requested update type
/rules/The path tells the server which configuration resource your browser is refreshing.
Update timing
2026-07-12T13:39:36ZEach request also tells the server when your browser is active with the extension installed.
04EvidenceSTORAGE DUMP
What's stored on your device

The identifier is saved in synced extension storage, so the extension can reuse it after the install request.

Locationchrome.storage.sync install data
Contents (JSON)
{
  "userId": "ai9WZkg2SXJUU20veDY0QTU5UmhKeUhYM0p2UU1NR082M21ua3ZvNW1SWWszR1JMYmlHY1ZYMUNOdm5rSW5jVnZ2b0taYStB",
  "delayFinal": 5
}
05EvidenceCODE COMPARE
The code that does this

Service worker code that stores and reuses the userId

What it actually does
Appending userId to every downloaded configuration URLServiceWorker.js
async download(url) {
        const {userId} = await chrome.storage.sync.get(['userId'])
        try {
            const response = await fetch(url + '?userId=' + userId);
            return response.json();
        }
        catch (e) {
            log(e);
        }
    }
First-install storage and follow-up URLsServiceWorker.js
chrome.runtime.onInstalled.addListener(async (details) => {
    if (details.reason === chrome.runtime.OnInstalledReason.INSTALL) {
        try {
            const {userId} = await chrome.storage.sync.get('userId');
            if (userId) {
                await fetch(START_URL+'?userId='+userId);
                chrome.runtime.setUninstallURL(UNINSTALL_URL+'?userId='+userId);
            }
            else {
                const data = await (await fetch(START_URL)).json();
                await chrome.storage.sync.set(data);
                await chrome.alarms.create(FINAL_ALARM_NAME, {delayInMinutes: data.delayFinal});
                chrome.runtime.setUninstallURL(UNINSTALL_URL+'?userId='+data.userId);
                await chrome.storage.sync.set({userId});
            }
        }
        finally {
            await chrome.storage.local.set({installed: true});
            extension.start();
        }
    }

});
Delayed final request with the same userIdServiceWorker.js
chrome.alarms.onAlarm.addListener(async function(alarm) {
    if (alarm.name === UPDATE_ALARM_NAME) {
        extension.update();
    }
    else if (alarm.name === FINAL_ALARM_NAME) {
        const {userId} = await chrome.storage.sync.get(['userId'])
        fetch(FINAL_URL + '?userId=' + userId);
    }
});
06EvidenceTHIRD PARTY LIST
External host receiving the identifier
  • eclipseadblocker.com

    Receives install, final, uninstall, rules, whitelist, net filter, and common CSS update requests with the userId query parameter.

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. The current listed version is unknown, which we have not unpacked yet.

  • Read and change your data on every site you visit

    <all_urls>

  • Schedule its own background tasks

    alarms

  • Run its own code inside the pages you visit

    scripting

  • 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

  • See every page you navigate to, as you navigate to it

    webNavigation

Updated 30 September 2026oielkmjpopfkakglicpehndjgbchlnmj