Is Free Adblocker for Chrome by Open Adblocker safe?
Open Adblocker is medium risk. The source includes built-in exceptions for Urban VPN analytics/stats hosts, loaded as trusted custom filters at startup. These let matching Urban VPN requests pass even when a user-defined blocking rule would otherwise match the same host.…
AI-generated. Findings may contain errors. Those marked Verified have been manually reviewed.
Publishers can request a review.
Findings
Urban VPN analytics rules are trusted by default
The source includes built-in exceptions for Urban VPN analytics/stats hosts, loaded as trusted custom filters at startup.
These let matching Urban VPN requests pass even when a user-defined blocking rule would otherwise match the same host.
- Severity
- Medium unwanted
- Type
- Unexpected
- CWE
- CWE-506
- Source
- Dynamic sandbox
You install or run the ad-blocking extension.
The extension loads built-in exception rules for Urban VPN analytics and stats hosts.
Those rules are configured as a trusted custom filter before filtering begins.
- Stats host exception@@||stats.urban-vpn.com^$important
Requests to this stats host can be allowed even when a blocking rule would otherwise match.
- Analytics host exception@@||analytics.urban-vpn.com^$important
Requests to this analytics host can be allowed even when a blocking rule would otherwise match.
- Urban VPN cid URLs@@||urban-vpn.com^*^cid=$document,subdocument,important
Urban VPN document and frame URLs containing a cid parameter can be exempted from blocking.
The shipped service worker hardcodes and trusts the Urban VPN exceptions
Built-in custom filter rules
service_worker.jsconst CUSTOM_FILTER_ID = 1001;const CUSTOM_FILTER_RULES = [ "@@||openadblocker.com^$important", "@@||stats.urban-vpn.com^$important", "@@||analytics.urban-vpn.com^$important", "@@||urban-vpn.com^*^cid=$document,subdocument,important",];Default configuration marks the custom filter trusted
service_worker.jsconst DEFAULT_EXTENSION_CONFIG = () => { const customRules = new es_FilterList(CUSTOM_FILTER_RULES.join("\n")); return { staticFiltersIds: [], customFilters: [ { filterId: CUSTOM_FILTER_ID, content: customRules.getContent(), trusted: true, conversionData: customRules.getConversionData(), }, ], trustedDomains: [], allowlist: [], userrules: { content: userRulesFilter.getContent(), conversionData: userRulesFilter.getConversionData(), }, verbose: true, filtersPath: "filters", ruleSetsPath: "filters/declarative", declarativeLogEnabled: true, settings: { assistantUrl: "assistant-inject.js", gpcScriptUrl: "gpc.js", hideDocumentReferrerScriptUrl: "hide-document-referrer.js", collectStats: true, allowlistEnabled: true, allowlistInverted: false, stealthModeEnabled: true, filteringEnabled: true, debugScriptlets: false, stealth: { blockChromeClientData: false, hideReferrer: false, hideSearchQueries: false, sendDoNotTrack: false, blockWebRTC: false, selfDestructThirdPartyCookies: false, selfDestructThirdPartyCookiesTime: 3600, selfDestructFirstPartyCookies: false, selfDestructFirstPartyCookiesTime: 3600, }, }, };};Startup path uses that default configuration
service_worker.jsconst setupAdGuard = () => { TsWebExtension.setLocalScriptRules(localScriptRules); adGuardSetupAsync();};const adGuardSetupAsync = async () => { const tsWebExtension = new TsWebExtension("/web-accessible-resources"); await tsWebExtension.initStorage(); const config = await getConfiguration(); await tsWebExtension.start(config); setUpTsWebExtensionMessages(tsWebExtension); adguard_setup_injections.push({ key: AdGuardIdentifiers._config, use: config, value: true }); adguard_setup_injections.push({ key: AdGuardIdentifiers._tsWebExtension, use: tsWebExtension, value: true }); inject(adguard_setup_injections); dispatcher().onWithClass(FiltersStateChangedListener); dispatcher().onWithClass(WebRTCStateChangedListener); dispatcher().onWithClass(RulesUpdatedListener); dispatcher().onWithClass(WhitelistUpdatedListener); const readyMessage = { type: AdGuardMessages.ready, force: true }; await dispatcher().sendMessage(readyMessage);};const getConfiguration = async () => { const config = DEFAULT_EXTENSION_CONFIG(); config.allowlist = getDomainsWithSubDomains(await whiteList().getDomains()); config.staticFiltersIds = await useInternalFilters().getEnabledFilters(); config.settings.stealth.blockWebRTC = await useInternalWebRTC().getState(); const userRules = new es_FilterList((await useInternalManualBlocking().getUserRules()).join("\n")); config.userrules = { content: userRules.getContent(), conversionData: userRules.getConversionData(), }; return config;};const setUpTsWebExtensionMessages = (tsWebExtension) => { const handler = tsWebExtension.getMessageHandler(); chrome.runtime.onMessage.addListener((message, sender, sendResponse) => { handler(message, sender).then((response) => sendResponse(response)); return true; });};- stats.urban-vpn.com
Urban VPN stats host protected by a built-in important exception rule.
- analytics.urban-vpn.com
Urban VPN analytics host protected by a built-in important exception rule.
- urban-vpn.com
Urban VPN document and frame URLs with a cid parameter are covered by the built-in exception rule.
Adblocker toggle sends active tab domain to analytics
Free Adblocker sends interaction analytics to analytics.openadblocker.com.
A captured request had a panelist ID, a CLICK action, and SETTINGS->OPEN_SETTINGS details; code adds the tab's domain on toggle clicks, unlike the store listing.
- Severity
- Medium unwanted
- Type
- Unexpected
- CWE
- CWE-359
- Source
- Dynamic sandbox
You click a control in the extension popup.
The adblocker toggle path is the one that adds the active tab's domain.
The extension sends an analytics event with a persistent identifier and interaction details.
For toggle clicks, the service worker adds the current tab hostname before posting the event.
Observed during dynamic analysis with panelistId, eventAction CLICK, and eventDetails SETTINGS->OPEN_SETTINGS; that run did not capture a domain-bearing toggle body.
- Content-Type
- application/json
- Persistent panelist IDA7fQp9Lm2ZxT4cB8nR0sYu
Lets multiple extension interactions be tied to the same browser profile over time.
- Active tab domaindeveloper.chrome.com
Shows which website was open when you used the adblocker toggle.
- Clicked controlADBLOCKER_TOGGLE
Shows which extension UI element you used.
- Action targetPAUSE
Shows what the extension control was trying to do at the time of the event.
- Extension version1.6.5
Adds software-version context to the interaction record.
- Event timestamp2026-07-12T22:48:38.676Z
Shows when the interaction happened.
Popup click to analytics POST path
Popup toggle click sends an adblocker_toggle user-activity event
popup/popup.jsconst useExternalAdBlocker = () => { if (!la) throw new Error('AdBlocker is not set up. Please call "setupExternalAdBlocker" first.'); return la},da = { class: "ad-blocker-toggle", "data-test": "adblocker-toggle"},ua = defineComponent({ __name: "toggle", setup(e) { const n = ref(!1), o = Wi(), s = useUserActivity(), d = useExternalAdBlocker(), u = runtime_core_esm_bundler_computed(() => o.app.isServicePage ? "Can’t block on this page" : o.app.isPaused ? "Resume for this website" : "Pause for this website"), onChange = async () => { if (o.app.isServicePage || n.value) return; n.value = !0; const e = o.app.isPaused; await s.click(Bi.adblockerToggle, { to: e ? Pi.play : Pi.pause, page: yo.HOME }); try { await d.toggle(!e), n.value = !1 } catch { n.value = !1 } }; return (e, s) => (openBlock(), createElementBlock("div", da, [On(Zi, { class: "ad-blocker-toggle__button", loading: n.value, onClick: onChange }, null, 8, ["loading"]), createBaseVNode("div", { class: normalizeClass([{ "ad-blocker-toggle__title--disabled": unref(o).app.isServicePage }, "ad-blocker-toggle__title"]), "data-test": "adblocker-toggle__title" }, toDisplayString(u.value), 3)])) }});AnalyticsService builds the JSON body and POSTs it with credentials included
service_worker.jslet AnalyticsService = class AnalyticsService { options; identifier; constructor($, z) { this.options = $, this.identifier = z } async track({ manual: $, action: z, details: _t }) { const Et = { clientVersion: this.options.version, eventSource: this.options.source, mode: (await chrome.management.getSelf()).installType, timestamp: Date.now(), panelistId: await this.identifier.getUserId(), manualEvent: $, eventDetails: this.upperCase(_t), eventAction: z }, Lt = `${this.options.api}/analytics/event`; await fetch(Lt, { body: JSON.stringify(Et), cache: "no-cache", credentials: "include", headers: { "Content-Type": "application/json" }, method: "POST" }) } upperCase($) { const z = {}; for (const _t in $) Object.assign(z, { [_t.toUpperCase()]: $[_t] }); return z }};Click-event strategy copies payload.domain into event details
service_worker.jsconst EVENT_STRATEGY = { [AnalyticsAction.click]: $ => { const { sessionId: z, element: _t, payload: Et } = $, Lt = ClickElementLinks.includes(_t); return { action: AnalyticsAction.click, manual: !0, details: { toolbar_session_id: z, id: _t.toUpperCase(), source: Et.page, to: Lt ? Et.to : Et.to.toUpperCase(), type: Lt ? ClickElementType.link : ClickElementType.button, ..."domain" in Et && { domain: Et.domain } } } }, [AnalyticsAction.extensionEnabled]: () => ({ action: AnalyticsAction.extensionEnabled, manual: !0, details: { source: "SYSTEM" } }), [AnalyticsAction.pageShown]: $ => { const { page: z, sessionId: _t } = $; return { action: AnalyticsAction.pageShown, manual: !1, details: { id: z, toolbar_session_id: _t } } }, [AnalyticsAction.toggle]: $ => { const { element: z, sessionId: _t, action: Et } = $; return { action: AnalyticsAction.toggle, manual: !0, details: { toggle_id: z.toUpperCase(), toolbar_session_id: _t, action: Et ? ToggleAction.on : ToggleAction.off } } }, [AnalyticsAction.settingsImportError]: $ => { const { reason: z } = $; return { action: AnalyticsAction.settingsImportError, manual: !0, details: { reason: z } } }, [AnalyticsAction.snackbarShown]: $ => { const { snackId: z, snackSuccess: _t } = $; return { action: AnalyticsAction.snackbarShown, manual: !0, details: { snack_id: z.toUpperCase(), snack_success: _t } } }};Service worker adds active-tab domain before tracking adblocker-toggle clicks
service_worker.jsanalytics_setup_injections = [{ key: AnalyticsIdentifiers.options, use: { api: "https://analytics.openadblocker.com/rest/v1", source: "OPEN_AD_BLOCKER", version: chrome.runtime.getManifest().version }, value: !0}, { key: AnalyticsIdentifiers.service, use: AnalyticsService}], setupInternalAnalytics = () => { chrome.management.onEnabled.addListener($ => { const z = { type: AnalyticsMessages.extensionEnabled, payload: $ }; dispatcher().sendMessage(z) }), chrome.permissions.onAdded.addListener(on_permissions_added_handler_onPermissionsAddedHandler), chrome.permissions.onRemoved.addListener(onPermissionsRemovedHandler), onIdentifierReady(handleIdentifierReady)}, handleIdentifierReady = async () => { inject(analytics_setup_injections), dispatcher().onWithClass(ExtensionEnabledListener), onUserActivity(analytics_setup_handleUserActivity)}, analytics_setup_handleUserActivity = async ({ message: $}) => { const z = { [UserActivityType.click]: async $ => { const z = $; if (z.element === ElementsUI.adblockerToggle) { const $ = await getActiveTabHelper(); $?.url && Object.assign(z.payload, { domain: getDomainHelper($.url) }) } const _t = buildEvent(AnalyticsAction.click, z); await utils_analytics().track(_t) }, [UserActivityType.visitPage]: async $ => { const z = buildEvent(AnalyticsAction.pageShown, $); await utils_analytics().track(z) }, [UserActivityType.toggle]: async $ => { const z = buildEvent(AnalyticsAction.toggle, $); await utils_analytics().track(z) }, [UserActivityType.settingsImportError]: async $ => { const z = buildEvent(AnalyticsAction.settingsImportError, $); await utils_analytics().track(z) }, [UserActivityType.snackbarShown]: async $ => { const z = buildEvent(AnalyticsAction.snackbarShown, $); await utils_analytics().track(z) } }, { type: _t } = $.payload; z[_t]?.($.payload)};Persistent panelist ID is stored in chrome.storage.local
service_worker.jslet IdentifierService = class IdentifierService { storage = makeDataAccessor("local", "USER_ID", { useCache: !1 }); async getUserId() { if (await this.storage.exists()) return await this.storage.read(); const $ = this.generateKey(); return await this.storage.write($), $ } generateKey() { let $ = ""; const z = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"; for (let _t = 0; _t < 22; _t++) $ += z.charAt(Math.floor(62 * Math.random())); return logger.info("User key generated:", $), $ }};- analytics.openadblocker.com
Receives extension interaction analytics, including the persistent panelist identifier and event details built by the service worker.