├── LICENSE ├── README.md ├── background.js ├── icons ├── LICENSE ├── cf-green-16.png ├── cf-green-32.png ├── cf-green-64.png ├── cf-grey-16.png ├── cf-grey-32.png ├── cf-grey-64.png ├── cf-orange-16.png ├── cf-orange-32.png ├── cf-orange-64.png ├── cf-red-16.png ├── cf-red-32.png ├── cf-red-64.png ├── cf.svg └── make.sh ├── manifest.json ├── popup.css ├── popup.html └── popup.js /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017 traktofon 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Detect Cloudflare (for Firefox) 2 | =============================== 3 | 4 | An extension for Mozilla Firefox which aims to detect whether the current page uses Cloudflare. 5 | It adds an icon to the browser toolbar which indicates the detection status. 6 | 7 | This is intended to help users detect which of the sites they visit is using Cloudflare, 8 | so that they can take the appropriate actions (e.g. changing their password) if they are 9 | worried that they are affected by the 10 | [cloudbleed bug](https://bugs.chromium.org/p/project-zero/issues/detail?id=1139). 11 | 12 | **Note**: This extension detects whether the page _currently_ uses Cloudflare. It cannot 13 | detect whether the page might have used Cloudflare in the past, especially during the time 14 | period where it was affected by cloudbleed (i.e. 2016-09-22 thru 2017-02-18). 15 | For this, you may check [this list](https://github.com/pirate/sites-using-cloudflare). 16 | 17 | 18 | Installation 19 | ------------ 20 | 21 | The extension is available on [addons.mozilla.org](https://addons.mozilla.org/en-US/firefox/addon/detect-cloudflare/). 22 | 23 | However, newest versions might appear there with some delay (hopefully this should be less than one day), 24 | so the impatient can install the extension from source manually: 25 | 26 | * Clone or download this repository. 27 | * In Firefox, go to [about:debugging](about:debugging). 28 | * Click "Load Temporary Add-on" and navigate to the file "manifest.json". Double-click/open it. 29 | * This will only load the add-on for the current Firefox session! 30 | 31 | 32 | How it Works 33 | ------------ 34 | 35 | The extension analyzes all HTTP(S) requests and checks whether any of them are served 36 | by Cloudflare proxies, as identified by certain headers in the HTTP(S) response. Based 37 | on the result of this analysis, the icon changes color: 38 | 39 | ![green](icons/cf-green-32.png) No requests were served by Cloudflare. 40 | 41 | ![orange](icons/cf-orange-32.png) Extenal resources were served by Cloudflare. 42 | 43 | ![red](icons/cf-red-32.png) The page itself was served by Cloudflare. 44 | 45 | Clicking on the icon will show a popup with detailed information about the Cloudflare-using domains. 46 | 47 | 48 | Notes 49 | ----- 50 | 51 | * This extension analyzes **all** requests made by the browser, though other extensions (e.g. adblockers) may block some requests before they are made. 52 | * When navigating forward/backward inside a tab, the detection status will reset to neutral. To get the correct status, the page needs to be reloaded. (Though nowadays the reload happens automatically for many webpages.) 53 | * This is my first WebExtension, and also my first JavaScript project. Use at your own risk! 54 | 55 | 56 | TODO 57 | ---- 58 | 59 | * Analyze performance impact. So far all test show negligible impact. 60 | 61 | -------------------------------------------------------------------------------- /background.js: -------------------------------------------------------------------------------- 1 | const iconColorAndDesc = { 2 | 0: { color: "grey", desc: "Detect Cloudflare" }, 3 | 1: { color: "green", desc: "This page doesn't seem to use Cloudflare." }, 4 | 2: { color: "orange", desc: "External resources on this page use Cloudflare." }, 5 | 3: { color: "red", desc: "This page uses Cloudflare!" } 6 | }; 7 | 8 | function Counter() { 9 | this.counts = new Map(); 10 | this.setCount = function(key,val) { 11 | this.counts.set(key, val); 12 | }; 13 | this.getCount = function(key) { 14 | if (!this.counts.has(key)) { return 0; } 15 | else { return this.counts.get(key); } 16 | }; 17 | this.incCount = function(key) { 18 | if (!this.counts.has(key)) { this.counts.set(key,1); } 19 | else { this.counts.set(key, this.counts.get(key)+1); } 20 | }; 21 | this.delCount = function(key) { 22 | this.counts.delete(key); 23 | }; 24 | } 25 | 26 | function mapToObject( map ) { 27 | obj = {}; 28 | map.forEach( function(val,key) { obj[key]=val; } ); 29 | return obj; 30 | } 31 | 32 | function CFInfo() { 33 | this.domainCounter = new Counter(); 34 | this.result = 0; 35 | // maybe more in the future 36 | } 37 | 38 | function CFInfoByTab() { 39 | this.info = new Map(); 40 | this.getInfo = function(id) { 41 | return this.info.get(id); 42 | } 43 | this.getOrCreate = function(id) { 44 | if (!this.info.has(id)) { this.info.set(id, new CFInfo()); } 45 | return this.info.get(id); 46 | }; 47 | this.delInfo = function(id) { 48 | this.info.delete(id); 49 | }; 50 | } 51 | 52 | var cfInfo = new CFInfoByTab(); 53 | 54 | function onError(e) { 55 | console.log(`CF-Detect-Background: ${e}`); 56 | } 57 | 58 | function getDomainFromURL( urltxt ) { 59 | try { 60 | var url = new URL(urltxt); 61 | return url.hostname; 62 | } catch(err) { 63 | return null; 64 | } 65 | } 66 | 67 | function updateStatus( tabId ) { 68 | var info = cfInfo.getInfo(tabId); 69 | if (info) { 70 | if (info.result >= 3) return; // no need for further updates 71 | var counts = info.domainCounter.counts; 72 | if (counts.size == 0) { 73 | info.result = 1; 74 | updateIcon( tabId, 1 ); 75 | } else { 76 | browser.tabs.get(tabId).then( function(tab) { 77 | var domain = getDomainFromURL(tab.url); 78 | if (counts.has(domain)) { 79 | info.result = 3; 80 | updateIcon( tabId, 3 ); 81 | } else { 82 | info.result = 2; 83 | updateIcon( tabId, 2 ); 84 | }}) 85 | .catch( onError ); 86 | } 87 | } else { 88 | updateIcon( tabId, 0 ); 89 | } 90 | } 91 | 92 | function updateIcon( tabId, result ) { 93 | var cd = iconColorAndDesc[result]; 94 | var color = cd.color; 95 | var title = cd.desc; 96 | browser.browserAction.setTitle({ 97 | tabId: tabId, 98 | title: title 99 | }); 100 | browser.browserAction.setIcon({ 101 | tabId: tabId, 102 | path: { 103 | 16: `icons/cf-${color}-16.png` , 104 | 32: `icons/cf-${color}-32.png` , 105 | 64: `icons/cf-${color}-64.png` 106 | } 107 | }); 108 | } 109 | 110 | function cfdetect( details ) { 111 | var headers = details.responseHeaders; 112 | var cf = false; 113 | for (var i=0; i" ] }, 174 | [ "responseHeaders" ] 175 | ); 176 | 177 | browser.webNavigation.onBeforeNavigate.addListener( handleBeforeNavigate ); 178 | 179 | browser.tabs.onUpdated.addListener( handleTabUpdate ); 180 | browser.tabs.onRemoved.addListener( handleTabClose ); 181 | browser.tabs.onReplaced.addListener( handleTabReplace ); 182 | 183 | browser.runtime.onConnect.addListener( handleConnect ); 184 | 185 | // vim: set expandtab ts=4 sw=4 : 186 | -------------------------------------------------------------------------------- /icons/LICENSE: -------------------------------------------------------------------------------- 1 | The drawing in "cf.svg" is derived from the "cloud" icon in 2 | Font Awesome by Dave Gandy - http://fontawesome.io . 3 | 4 | It is licensed under the SIL Open Font License 1.1, 5 | http://scripts.sil.org/OFL . 6 | -------------------------------------------------------------------------------- /icons/cf-green-16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/traktofon/cf-detect/fbf7b1f68dedddd213805d497caf5aaa7a102dd8/icons/cf-green-16.png -------------------------------------------------------------------------------- /icons/cf-green-32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/traktofon/cf-detect/fbf7b1f68dedddd213805d497caf5aaa7a102dd8/icons/cf-green-32.png -------------------------------------------------------------------------------- /icons/cf-green-64.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/traktofon/cf-detect/fbf7b1f68dedddd213805d497caf5aaa7a102dd8/icons/cf-green-64.png -------------------------------------------------------------------------------- /icons/cf-grey-16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/traktofon/cf-detect/fbf7b1f68dedddd213805d497caf5aaa7a102dd8/icons/cf-grey-16.png -------------------------------------------------------------------------------- /icons/cf-grey-32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/traktofon/cf-detect/fbf7b1f68dedddd213805d497caf5aaa7a102dd8/icons/cf-grey-32.png -------------------------------------------------------------------------------- /icons/cf-grey-64.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/traktofon/cf-detect/fbf7b1f68dedddd213805d497caf5aaa7a102dd8/icons/cf-grey-64.png -------------------------------------------------------------------------------- /icons/cf-orange-16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/traktofon/cf-detect/fbf7b1f68dedddd213805d497caf5aaa7a102dd8/icons/cf-orange-16.png -------------------------------------------------------------------------------- /icons/cf-orange-32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/traktofon/cf-detect/fbf7b1f68dedddd213805d497caf5aaa7a102dd8/icons/cf-orange-32.png -------------------------------------------------------------------------------- /icons/cf-orange-64.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/traktofon/cf-detect/fbf7b1f68dedddd213805d497caf5aaa7a102dd8/icons/cf-orange-64.png -------------------------------------------------------------------------------- /icons/cf-red-16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/traktofon/cf-detect/fbf7b1f68dedddd213805d497caf5aaa7a102dd8/icons/cf-red-16.png -------------------------------------------------------------------------------- /icons/cf-red-32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/traktofon/cf-detect/fbf7b1f68dedddd213805d497caf5aaa7a102dd8/icons/cf-red-32.png -------------------------------------------------------------------------------- /icons/cf-red-64.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/traktofon/cf-detect/fbf7b1f68dedddd213805d497caf5aaa7a102dd8/icons/cf-red-64.png -------------------------------------------------------------------------------- /icons/cf.svg: -------------------------------------------------------------------------------- 1 | 2 | 17 | 19 | 20 | 22 | image/svg+xml 23 | 25 | 26 | 28 | 29 | 31 | 33 | 35 | 37 | 39 | 41 | 43 | 45 | 47 | 49 | 50 | 51 | 52 | 54 | 76 | 80 | 84 | 85 | 90 | 93 | 99 | 104 | 109 | 114 | 127 | 140 | 153 | 166 | 167 | 168 | -------------------------------------------------------------------------------- /icons/make.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | colors="red:#cc0000 green:#00cc00 orange:#ddaa00 grey:#cccccc" 4 | 5 | for res in 16 32 64; do 6 | for color in ${colors}; do 7 | colname=${color%:*} 8 | colspec=${color#*:} 9 | convert -background none cf.svg \ 10 | +level-colors "${colspec}," \ 11 | -colorspace RGB \ 12 | -resize "${res}" \ 13 | -colorspace sRGB \ 14 | "cf-${colname}-${res}.png" 15 | done 16 | done 17 | 18 | -------------------------------------------------------------------------------- /manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "manifest_version": 2, 3 | "name": "Detect Cloudflare", 4 | "homepage_url": "https://github.com/traktofon/cf-detect", 5 | "description": "Adds an icon to the toolbar which indicates whether the current page uses Cloudflare. If it does, the icon changes color. Detection is performed by analyzing the response headers of all requests.", 6 | "version": "0.7", 7 | "icons": { 8 | "16": "icons/cf-grey-16.png", 9 | "32": "icons/cf-grey-32.png", 10 | "64": "icons/cf-grey-64.png" 11 | }, 12 | "permissions": [ 13 | "webRequest", 14 | "webNavigation", 15 | "tabs", 16 | "" 17 | ], 18 | "background": { 19 | "scripts": [ "background.js" ] 20 | }, 21 | "browser_action": { 22 | "browser_style": true, 23 | "default_title": "Indicates whether this page uses Cloudflare", 24 | "default_icon": { 25 | "16": "icons/cf-grey-16.png", 26 | "32": "icons/cf-grey-32.png", 27 | "64": "icons/cf-grey-64.png" 28 | }, 29 | "default_popup": "popup.html" 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /popup.css: -------------------------------------------------------------------------------- 1 | body { 2 | padding: 0em 1em; 3 | } 4 | 5 | p#status { 6 | font-weight: bold; 7 | } 8 | 9 | ul { 10 | margin-top: -1ex; 11 | padding-left: 1em; 12 | } 13 | 14 | .count { 15 | color: #cc0000; 16 | } 17 | -------------------------------------------------------------------------------- /popup.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 |
9 |

Detection Status.

10 |
11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /popup.js: -------------------------------------------------------------------------------- 1 | const statusText = { 2 | 0: "No requests have been processed yet.", 3 | 1: "No requests were served by Cloudflare.", 4 | 2: "Requests for these domains were served by Cloudflare:", 5 | 3: "Requests for these domains were served by Cloudflare:", 6 | 99: "Detection result unavailable." 7 | }; 8 | 9 | var getTab = browser.tabs.query( { active:true, currentWindow:true } ); 10 | 11 | getTab.then( function( tabs ) { 12 | var tab = tabs[0]; 13 | var port = browser.runtime.connect(); 14 | port.postMessage(tab.id); 15 | port.onMessage.addListener( function(msg) { 16 | port.disconnect(); 17 | if (msg) { 18 | writeStatus(msg.result); 19 | populatePopup(msg.counts); 20 | } else { 21 | writeStatus(0); 22 | } 23 | }); 24 | }) 25 | .catch( function( error ) { 26 | writeStatus(99); 27 | console.log(`CF-Detect-Popup: ${error}`); 28 | }); 29 | 30 | function writeStatus( st ) { 31 | var p = document.getElementById("status"); 32 | p.textContent = statusText[st]; 33 | } 34 | 35 | function populatePopup( domainCounts ) { 36 | var ndomain = 0; 37 | var div = document.getElementById("top"); 38 | var ul = document.createElement("ul"); 39 | for (var domain in domainCounts) { 40 | if (!domainCounts.hasOwnProperty(domain)) continue; 41 | ++ndomain; 42 | var count = domainCounts[domain]; 43 | var li = document.createElement("li"); 44 | var text = document.createTextNode(`${domain}: `); 45 | var span = document.createElement("span"); 46 | span.setAttribute("class", "count"); 47 | span.textContent = `${count}`; 48 | li.appendChild(text); 49 | li.appendChild(span); 50 | ul.appendChild(li); 51 | } 52 | if (ndomain>0) div.appendChild(ul); 53 | } 54 | 55 | // vim: set expandtab ts=4 sw=4 : 56 | --------------------------------------------------------------------------------