commit e90ff542b08979968a04a61c1650bf72598a084c Author: libroot Date: Sun Sep 28 06:09:19 2025 +0000 Initial commit diff --git a/README b/README new file mode 100644 index 0000000..ac1cadf --- /dev/null +++ b/README @@ -0,0 +1,7 @@ +# Cloudflare Blocker extension + +* Detects Cloudflare-related headers in outgoing requests. +* Blocks the request if Cloudflare is present. +* Toggle the blocker on/off via the extension popup. + +Available from Mozilla Add-ons: [https://addons.mozilla.org/en-US/firefox/addon/cloudflare-blocker/](https://addons.mozilla.org/en-US/firefox/addon/cloudflare-blocker/) diff --git a/background.js b/background.js new file mode 100644 index 0000000..52a55b7 --- /dev/null +++ b/background.js @@ -0,0 +1,69 @@ +let blockingEnabled = true; + +chrome.runtime.onMessage.addListener((message, sender, sendResponse) => { + if (message.action === 'toggleBlocking') { + blockingEnabled = message.enabled; + } + + if (message.action === 'getBlockingState') { + sendResponse({ action: 'setBlockingState', enabled: blockingEnabled }); + } +}); + +chrome.webRequest.onHeadersReceived.addListener( + function(details) { + if (!blockingEnabled) { + return { cancel: false }; + } + + // These are Cloudflare's headers, taken from here: + // https://developers.cloudflare.com/fundamentals/reference/http-headers/ + const cloudflare_headers = [ + 'cf-connecting-ip', + 'cf-connecting-ipv6', + 'cf-ew-via', + 'cf-Pseudo-ipv4', + 'cf-ray', + 'cf-ipcountry', + 'cf-visitor', + 'cf-worker' + ]; + // You might want capture all headers prefixed with "cf-": + // const cf_header = details.responseHeaders.find(header => + // header.name.toLowerCase().startsWith('cf-') + // ); + + const cf_header = details.responseHeaders.find(header => + cloudflare_headers.includes(header.name.toLowerCase()) + ); + + if (cf_header) { + console.log('Cloudflare header found:', cf_header); + + const query_params = new URLSearchParams({ + name: cf_header.name, + value: cf_header.value, + url: details.url + }).toString(); + + /* + * It would be cool to pass the tab URL in the query params, but how? + * When accessing chrome.tabs here, the active tab URL is "about:newtab". + * Because we block the request immediately and the tab URL state doesn't + * never update to the URL we tried to access. I'd like to pass the URL + * to the web.archive.org/tab_url_here in blocked.html. + * Email contact@libroot.org if you know a solution! + */ + + // Redirect to our custom blocked.html page + const url = chrome.runtime.getURL('blocked.html') + '?' + query_params; + console.log('Redirecting to blocked page:', url); + chrome.tabs.update(details.tabId, { url: url }); + + // Block the request, reject Cloudflare! + return { cancel: true }; + } + }, + { urls: [''] }, + ['blocking', 'responseHeaders'] +); diff --git a/blocked.html b/blocked.html new file mode 100644 index 0000000..0489208 --- /dev/null +++ b/blocked.html @@ -0,0 +1,40 @@ + + + + + + Cloudflare page blocked + + + +

This page has been blocked by the Cloudflare Blocker extension due to a Cloudflare header.

+ +

+ Blocked Header: +

Loading...
+

+ +

+ Blocked response URL: +

Loading...
+

+ +
+

You can probably view the page on the Wayback Machine.

+ + + + diff --git a/blocked.js b/blocked.js new file mode 100644 index 0000000..b31eead --- /dev/null +++ b/blocked.js @@ -0,0 +1,21 @@ +function getUrlParams() { + const urlParams = new URLSearchParams(window.location.search); + return { + name: urlParams.get('name'), + value: urlParams.get('value'), + url: urlParams.get('url') + }; +} + +document.addEventListener('DOMContentLoaded', function() { + const params = getUrlParams(); + + if (params.name && params.value) { + const header_info = `Name: ${params.name}\nValue: ${params.value}`; + document.getElementById('header_info').textContent = header_info; + } + + if (params.url) { + document.getElementById('url').textContent = params.url; + } +}); diff --git a/icons/icon.png b/icons/icon.png new file mode 100644 index 0000000..59b7927 Binary files /dev/null and b/icons/icon.png differ diff --git a/manifest.json b/manifest.json new file mode 100644 index 0000000..35ee1c9 --- /dev/null +++ b/manifest.json @@ -0,0 +1,28 @@ +{ + "name": "Cloudflare Blocker", + "version": "1.0.0", + "author": "libroot.org", + "developer": { + "name": "libroot", + "url": "https://libroot.org/posts/Cloudflare-Blocker-Firefox-extension" + }, + "description": "Blocks requests if they have Cloudflare's headers, and shows a blocked page.", + "manifest_version": 2, + "permissions": [ + "webRequest", + "webRequestBlocking", + "tabs", + "" + ], + "background": { + "scripts": ["background.js"], + "persistent": false + }, + "browser_action": { + "default_popup": "popup.html", + "default_icon": "icons/icon.png" + }, + "web_accessible_resources": [ + "blocked.html" + ] +} diff --git a/popup.html b/popup.html new file mode 100644 index 0000000..5b31137 --- /dev/null +++ b/popup.html @@ -0,0 +1,53 @@ + + + + + + Cloudflare Blocker + + + + +

Cloudflare Blocker

+
+
+
+

Blocking is ON

+ + + + diff --git a/popup.js b/popup.js new file mode 100644 index 0000000..0bf0c0c --- /dev/null +++ b/popup.js @@ -0,0 +1,32 @@ +const toggle = document.getElementById('toggle'); +const status = document.getElementById('status'); + +chrome.runtime.sendMessage({ action: 'getBlockingState' }, (response) => { + if (response && response.enabled !== undefined) { + const blockingEnabled = response.enabled; + + if (blockingEnabled) { + toggle.classList.add('active'); + status.textContent = 'Blocking is ON'; + } else { + toggle.classList.remove('active'); + status.textContent = 'Blocking is OFF'; + } + } +}); + +toggle.addEventListener('click', function () { + const isActive = toggle.classList.toggle('active'); + + if (isActive) { + status.textContent = 'Blocking is ON'; + } else { + status.textContent = 'Blocking is OFF'; + } + + chrome.runtime.sendMessage({ + action: 'toggleBlocking', + enabled: isActive + }); +}); +