Is FadBlock Origin: Friendly YouTube™ Adblock safe?

Medium risk

FadBlock Origin fetches its ad-detection rules from a remote GitHub Pages endpoint on install and update.

On install, update, and user-triggered refresh, the extension requests a JSON config file from 0x48piraj.github.io/fadblock/fadblock.json. The response contains CSS selector strings that determine which DOM elements on YouTube are treated as ads, video players, and skip buttons. These selectors are written to local extension storage and used by the content script to locate and interact with ad elements.

0x48pirajv2.0Chrome Web Store
45Risk

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

Publishers can request a review.

Findings

Remote YouTube selector config drives page behavior

FadBlock Origin fetches a selector config from GitHub Pages on install, update, and via the popup Update button.

We saw a GET to it and a storage write with values like isAd=.ad-interrupting, videoPlayer=.video-stream; content.js uses them.

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

You install or update the extension, or press Update in its popup.

Those actions call the same selector-refresh path.

The extension did this

The extension downloads selector names and uses them to decide which YouTube elements to handle.

The downloaded values are stored locally before the content script reads them on YouTube pages.

Captured request
GEThttps://0x48piraj.github.io/fadblock/fadblock.json

200 OK; the response was parsed as JSON, and observed local storage included isAd=.ad-interrupting and videoPlayer=.video-stream.

Selector values observed or written from the remote configuration
  • Ad marker selector
    .ad-interrupting

    This tells the extension which YouTube page element means an ad is currently present.

  • Video player selector
    .video-stream

    This tells the extension which video element it can fast-forward when it decides an ad is playing.

  • Stored selector fields
    isAd, videoContainer, videoPlayer, previewText, previewTextAlt, staticAds, skipButton, skipButtonAlt, surveyButton

    The remote file can set the selector names the extension later uses while processing YouTube pages.

The code that does this

Remote config fetch, storage write, and selector use in shipped code

Readable version

Background fetch and storage write

js/background.js
function init() {  fetch("https://0x48piraj.github.io/fadblock/fadblock.json", {    method: "GET"  }).then((function(t) {    return t.json()  })).then((function(t) {    chrome.storage.local.set({      isAd: t.isAd,      videoContainer: t.videoContainer,      videoPlayer: t.videoPlayer,      previewText: t.previewText,      previewTextAlt: t.previewTextAlt,      staticAds: t.staticAds,      skipButton: t.skipButton,      skipButtonAlt: t.skipButtonAlt,      surveyButton: t.surveyButton    })  })), extpay.getUser().then((t => {    t.paid || extpay.openTrialPage()  }))}

Install and update trigger

js/background.js
chrome.runtime.onInstalled.addListener((t => {  switch (t.reason) {    case "install":      console.info("EXTENSION INSTALLED"), chrome.storage.local.set({        skipCount: 0      }), init();      break;    case "update":      chrome.storage.local.get(["skipCount"]).then((t => void 0 !== t.skipCount ? console.info("EXTENSION UPDATED") : chrome.storage.local.set({        skipCount: 0      }))), init();      break;    default:      chrome.storage.local.get(["skipCount"]).then((t => void 0 !== t.skipCount ? console.info("BROWSER UPDATED") : chrome.storage.local.set({        skipCount: 0      }))), init()  }}))

Content script selector use

js/content.js
const extpay = ExtPay("fadblock"),  taimuRipu = async () => {    await new Promise(((e, t) => {      setTimeout((() => {        chrome.storage.local.get(["skipCount", "isAd", "videoContainer", "videoPlayer", "previewText", "previewTextAlt", "skipButton", "skipButtonAlt", "surveyButton", "staticAds"], (function(e) {          const t = document.querySelector(e.isAd),            i = document.querySelector(e.previewText),            o = document.querySelector(e.previewTextAlt);          t && (i || o) && chrome.runtime.sendMessage({            message: "taimu-ripu"          }), e.staticAds.forEach((e => {            const t = document.querySelector(e);            t && (t.style.display = "none")          }))        })), e()      }), 100)    })), taimuRipu()  };taimuRipu();

Popup update button fetch

js/popup.js
function getArtefacts() {  fetch("https://0x48piraj.github.io/fadblock/fadblock.json", {    method: "GET"  }).then((function(e) {    return e.json()  })).then((function(e) {    chrome.storage.local.set({      isAd: e.isAd,      videoContainer: e.videoContainer,      videoPlayer: e.videoPlayer,      previewText: e.previewText,      previewTextAlt: e.previewTextAlt,      staticAds: e.staticAds,      skipButton: e.skipButton,      skipButtonAlt: e.skipButtonAlt,      surveyButton: e.surveyButton    })  }))}
Remote host involved in the selector update
    • 0x48piraj.github.io

    GitHub Pages host serving fadblock.json, the selector configuration consumed by the extension.

What it can do

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

    https://*.youtube.com/*

  • Read and change your data on www.youtube.com

    https://www.youtube.com/*

  • Run its own code inside the pages you visit

    scripting

  • Store data in your browser

    storage

Updated 21 September 2026lmnhcklabcehiohmmeihcheoegomkghm