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:
+
+
+
+
+ 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
+ });
+});
+