├── .gitattributes ├── LICENSE.txt ├── README.md ├── StoreUI ├── 1280x800.png ├── 440x280.png ├── AllSecure.png ├── SomeInsecure.png ├── icon128b.png ├── icon64.png └── nonsecure.png ├── TODO.txt ├── background.js ├── images ├── HSTSyes.png ├── HTTPSno.png ├── HTTPSyes.png ├── IsHSTS.png ├── IsHTTPS.png ├── icon128.png ├── icon16.png ├── icon19.png ├── icon32.png ├── icon38.png └── icon48.png ├── injected.css ├── injected.js ├── manifest.json ├── options.css ├── options.html ├── options.js ├── popup.css ├── popup.html └── popup.js /.gitattributes: -------------------------------------------------------------------------------- 1 | # Auto detect text files and perform LF normalization 2 | * text=auto -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | Copyright (c) 2016 by the moarTLS Authors. 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy 4 | of this software and associated documentation files (the "Software"), to deal 5 | in the Software without restriction, including without limitation the rights 6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | copies of the Software, and to permit persons to whom the Software is 8 | furnished to do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in 11 | all copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 | THE SOFTWARE. 20 | 21 | 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # moartls 2 | moarTLS is a Browser Extension that flags non-secure links and downloads. 3 | 4 | Learn more about the extension in the [Announcement Blog Post](https://textplain.wordpress.com/2016/03/17/seek-and-destroy-non-secure-references-using-the-moartls-analyzer/) 5 | 6 | Install the Edge / Chrome extension from the [Chrome WebStore](https://chrome.google.com/webstore/detail/moartls-analyzer/ldfbacdbackkjhclmhnjabngnppnkagh) 7 | 8 | Install the Firefox extension from the [Mozilla Addons site](https://addons.mozilla.org/en-US/firefox/addon/moartls/?src=cb-dl-rating) 9 | 10 | Install the Edge Legacy version from the [Microsoft Store page](https://www.microsoft.com/en-us/p/moartls-analyzer/9nw480sp5d2c#activetab=pivot:overviewtab) 11 | 12 | The Extension works with Opera but their store rejected the extension because they don't like mention of "Chrome" and I was too lazy to fix it. 13 | -------------------------------------------------------------------------------- /StoreUI/1280x800.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ericlaw1979/moartls/cc3e8a5d30fd9e2dfcec7d24ce4d42041abd7e13/StoreUI/1280x800.png -------------------------------------------------------------------------------- /StoreUI/440x280.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ericlaw1979/moartls/cc3e8a5d30fd9e2dfcec7d24ce4d42041abd7e13/StoreUI/440x280.png -------------------------------------------------------------------------------- /StoreUI/AllSecure.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ericlaw1979/moartls/cc3e8a5d30fd9e2dfcec7d24ce4d42041abd7e13/StoreUI/AllSecure.png -------------------------------------------------------------------------------- /StoreUI/SomeInsecure.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ericlaw1979/moartls/cc3e8a5d30fd9e2dfcec7d24ce4d42041abd7e13/StoreUI/SomeInsecure.png -------------------------------------------------------------------------------- /StoreUI/icon128b.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ericlaw1979/moartls/cc3e8a5d30fd9e2dfcec7d24ce4d42041abd7e13/StoreUI/icon128b.png -------------------------------------------------------------------------------- /StoreUI/icon64.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ericlaw1979/moartls/cc3e8a5d30fd9e2dfcec7d24ce4d42041abd7e13/StoreUI/icon64.png -------------------------------------------------------------------------------- /StoreUI/nonsecure.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ericlaw1979/moartls/cc3e8a5d30fd9e2dfcec7d24ce4d42041abd7e13/StoreUI/nonsecure.png -------------------------------------------------------------------------------- /TODO.txt: -------------------------------------------------------------------------------- 1 | TODO: Reintroduce support for poking into ShadowDOMs; see https://bugs.chromium.org/p/chromium/issues/detail?id=778816#c25 2 | 3 | 4 | 5 | HTTP BAD - Detect CC and Password 6 | 7 | 8 | NOTE: DEEP SELECTOR NO LONGER WORKS. :( 9 | https://bugs.chromium.org/p/chromium/issues/detail?id=937746#c99 10 | 11 | Password is easy: 12 | document.querySelectorAll("* /deep/ input[type='password']"); 13 | 14 | Credit card is complex: 15 | https://cs.chromium.org/chromium/src/components/autofill/core/browser/autofill_regex_constants.cc?sq=package:chromium&dr=CSs&rcl=1473923811&l=140 16 | 17 | 18 | 19 | chrome.management.getSelf( (o) => { 20 | lnkVersion.textContent = lnkVersion.textContent + o.installType; <-- "development" if self-installed 21 | } ); 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | ================= 30 | 31 | if (typeof msBrowser !== 'undefined') { chrome = msBrowser; } else if (typeof browser !== 'undefined') { chrome = browser;} 32 | 33 | scroll link into view 34 | run CSS / HTML / Script lint 35 | 36 | 0. Inject NOSY image? 37 | 1. Hash file downloads (https://developer.chrome.com/extensions/nativeMessaging#native-messaging-host) 38 | 39 | You should always provide a 128x128 icon; it's used during installation and by the Chrome Web Store. 40 | Extensions should also provide a 48x48 icon, which is used in the extensions management page (chrome://extensions). 41 | You can also specify a 16x16 icon to be used as the favicon for an extension's pages. 42 | 43 | Use PNG format, transparency is good. 44 | 45 | "icons": { "16": "icon16.png", 46 | "48": "icon48.png", 47 | "128": "icon128.png" }, 48 | 49 | For example, Windows often requires 32-pixel icons, and if the app includes a 32-pixel icon, Chrome will choose that instead of shrinking a 48-pixel icon down. However, you should ensure that all of your icons are square, or unexpected behavior may result. 50 | If the icon is square, the main image should be 96x96px with a 16px transparent padding. 51 | 52 | Include a 38x38 for retina macs: http://stackoverflow.com/questions/18555085/how-do-i-specify-an-chrome-extension-icon-popup-icon-for-retina-macs 53 | 54 | https://developer.chrome.com/webstore/images 55 | 56 | https://developer.chrome.com/webstore/branding 57 | 58 | https://developer.chrome.com/webstore/publish?hl=en-US#step2 59 | 60 | Applications and themes require at least one screenshot. Extensions may have no screenshots, but such extensions won't be shown in the gallery. 61 | Provide preferably 4 or 5 screenshots of your app (up to a maximum of 5). If your app supports multiple locales, you can provide locale-specific screenshots. Your screenshot should have square corners and no padding (full bleed). 62 | We prefer screenshots to be 1280x800 pixels in size, as they will be used in future high dpi displays. Currently, the Chrome Web Store will downscale all screenshots to 640x400, so if your screenshots will not look good downscaled (eg. have a lot of text) or if 1280x800 is too big for your app (screenshot of low resolution game), we also support 640x400 pixels screenshots. 63 | -------------------------------------------------------------------------------- /background.js: -------------------------------------------------------------------------------- 1 | "use strict" 2 | 3 | // Background serviceworker 4 | // https://developer.chrome.com/extensions/event_pages 5 | 6 | // Our background sw isn't persistent. 7 | chrome.runtime.onStartup.addListener(()=> { init(); }); 8 | // onInstalled when user uses chrome://extensions page to reload 9 | chrome.runtime.onInstalled.addListener(() => { init(); }); 10 | 11 | function init() 12 | { 13 | // Add Badge notification if this is a dev-install 14 | chrome.management.getSelf( (o)=>{ 15 | if (o.installType === "development") { 16 | chrome.action.setBadgeText( {text: "dev"} ); 17 | } 18 | }); 19 | } 20 | 21 | chrome.downloads.onCreated.addListener(function(item) { 22 | // https://developer.chrome.com/extensions/downloads#type-DownloadItem 23 | 24 | // Note: The download manager "creates" downloads for previously-downloaded items 25 | // So we need to look for "in_progress" downloads only. 26 | // 27 | // TODO: Can we do anything useful with the item.mime property? 28 | // 29 | if (item.state == "in_progress") { 30 | const storage = (chrome.storage.sync ? chrome.storage.sync : chrome.storage.local); 31 | storage.get(["bWarnOnNonSecureDownloads"], function(bWarnOnNonSecureDownloads) { 32 | if (!bWarnOnNonSecureDownloads) return; 33 | 34 | if ((item.url.substring(0, 5) == "http:") || 35 | (item.referrer && item.referrer.substring(0, 5) == "http:")) 36 | { 37 | var sReferer = (item.referrer) ? ("\nvia\n" + item.referrer) : ""; 38 | 39 | // Manifest v3 does not support alert(), so we need to use a notification. 40 | var options = { 41 | type: 'basic', 42 | title: 'Non-Secure Download was detected', 43 | message: `Download of:\n${item.url}${sReferer}`, 44 | iconUrl: 'images/icon128.png' 45 | }; 46 | chrome.notifications.create(options); 47 | } 48 | }); 49 | } 50 | }); -------------------------------------------------------------------------------- /images/HSTSyes.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ericlaw1979/moartls/cc3e8a5d30fd9e2dfcec7d24ce4d42041abd7e13/images/HSTSyes.png -------------------------------------------------------------------------------- /images/HTTPSno.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ericlaw1979/moartls/cc3e8a5d30fd9e2dfcec7d24ce4d42041abd7e13/images/HTTPSno.png -------------------------------------------------------------------------------- /images/HTTPSyes.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ericlaw1979/moartls/cc3e8a5d30fd9e2dfcec7d24ce4d42041abd7e13/images/HTTPSyes.png -------------------------------------------------------------------------------- /images/IsHSTS.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ericlaw1979/moartls/cc3e8a5d30fd9e2dfcec7d24ce4d42041abd7e13/images/IsHSTS.png -------------------------------------------------------------------------------- /images/IsHTTPS.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ericlaw1979/moartls/cc3e8a5d30fd9e2dfcec7d24ce4d42041abd7e13/images/IsHTTPS.png -------------------------------------------------------------------------------- /images/icon128.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ericlaw1979/moartls/cc3e8a5d30fd9e2dfcec7d24ce4d42041abd7e13/images/icon128.png -------------------------------------------------------------------------------- /images/icon16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ericlaw1979/moartls/cc3e8a5d30fd9e2dfcec7d24ce4d42041abd7e13/images/icon16.png -------------------------------------------------------------------------------- /images/icon19.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ericlaw1979/moartls/cc3e8a5d30fd9e2dfcec7d24ce4d42041abd7e13/images/icon19.png -------------------------------------------------------------------------------- /images/icon32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ericlaw1979/moartls/cc3e8a5d30fd9e2dfcec7d24ce4d42041abd7e13/images/icon32.png -------------------------------------------------------------------------------- /images/icon38.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ericlaw1979/moartls/cc3e8a5d30fd9e2dfcec7d24ce4d42041abd7e13/images/icon38.png -------------------------------------------------------------------------------- /images/icon48.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ericlaw1979/moartls/cc3e8a5d30fd9e2dfcec7d24ce4d42041abd7e13/images/icon48.png -------------------------------------------------------------------------------- /injected.css: -------------------------------------------------------------------------------- 1 | a.moarTLSUnsecure { 2 | background: rgba(222, 106, 106, 0.6) !important; 3 | border-radius: 6px; 4 | border: 2px solid #DC0000; 5 | padding: 4px 4px 4px 4px; 6 | margin: 2px 2px 2px 2px; 7 | transition: background 1s ease; 8 | } 9 | 10 | a.moarTLSUnsecure img { 11 | -webkit-filter:sepia(1) hue-rotate(290deg); 12 | } 13 | 14 | body.moarTLSUnsecure { 15 | background: rgba(222, 106, 106, 0.5) !important; 16 | transition: background 1s ease; 17 | } 18 | 19 | form.moarTLSUnsecure { 20 | background: rgba(222, 106, 106, 0.6) !important; 21 | border-radius: 4px; 22 | border: 2px dashed #B93737; 23 | padding: 3px; 24 | margin: 3px; 25 | transition: 0.5s ease; 26 | } 27 | 28 | input.moarTLSUnsecure, 29 | img.moarTLSUnsecure { 30 | transform:rotate(180deg); 31 | transition: transform 0.5s ease; 32 | } -------------------------------------------------------------------------------- /injected.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | { 4 | if (typeof chrome === "undefined") var chrome = browser; 5 | 6 | // Remove URI-wrappers added by e.g. hotmail 7 | function unwrapUri(str) { 8 | let sl = /https:\/\/\w+\.safelinks.protection.outlook.com.*/ 9 | if (sl.test(str)) { 10 | return decodeURIComponent(str.substring(str.indexOf('url=')+4)); 11 | } 12 | return str; 13 | } 14 | 15 | function isNonsecure(str) { 16 | str = str.toLowerCase(); 17 | if (str.startsWith("http:") || str.startsWith("ftp:")) return true; 18 | return false; 19 | } 20 | 21 | function findUnsecureImages() 22 | { 23 | const imgs = document.querySelectorAll("img"); 24 | for (let i = 0; i < imgs.length; i++) { 25 | if (isNonsecure(unwrapUri(imgs[i].src))) { 26 | arrNonSecureImages.push(imgs[i]); 27 | } 28 | } 29 | 30 | const imginputs = document.querySelectorAll("input[type='image']"); 31 | for (let i = 0; i < imginputs.length; i++) { 32 | if (isNonsecure(unwrapUri(imginputs[i].src))) { 33 | arrNonSecureImages.push(imginputs[i]); 34 | } 35 | } 36 | } 37 | 38 | function markUnsecureImages() 39 | { 40 | for (let i = 0; i < arrNonSecureImages.length; i++) { 41 | arrNonSecureImages[i].classList.add("moarTLSUnsecure"); 42 | } 43 | } 44 | 45 | function markUnsecureDocument() 46 | { 47 | // Entire frame is unsecure? 48 | const sProt = document.location.protocol.toLowerCase(); 49 | if ((document.body) && 50 | ((sProt === "http:") || (sProt === "ftp:"))) { 51 | document.body.classList.add("moarTLSUnsecure"); 52 | } 53 | } 54 | 55 | const arrUnsecure = []; 56 | const arrNonSecureImages = []; 57 | let cLinks = 0; 58 | 59 | findUnsecureImages(); 60 | 61 | { 62 | if (chrome.storage) 63 | { 64 | const storage = (chrome.storage.sync ? chrome.storage.sync : chrome.storage.local); 65 | storage.get("bRotateNonSecureImages", function(obj) { 66 | if (obj && (false === obj.bRotateNonSecureImages)) return; 67 | markUnsecureImages(); 68 | }); 69 | 70 | storage.get("bWarnOnNonSecureDocument", function(obj) { 71 | if (obj && (false === obj.bWarnOnNonSecureDocument)) return; 72 | markUnsecureDocument(); 73 | }); 74 | } 75 | else 76 | { 77 | markUnsecureImages(); 78 | } 79 | } 80 | 81 | { 82 | const forms = document.querySelectorAll("form[action]"); 83 | for (let i = 0; i < forms.length; i++) { 84 | const thisForm = forms[i]; 85 | if (thisForm.getAttribute("action")[0] === "#") continue; // Not a cross-page 'action' 86 | cLinks++; 87 | const sUri = unwrapUri((typeof thisForm.action === "string") ? 88 | thisForm.action.toLowerCase() 89 | : thisForm.getAttribute("action").toLowerCase()); 90 | if (isNonsecure(sUri)) { 91 | arrUnsecure.push(sUri); 92 | thisForm.title = "Form target is: " + sUri; 93 | thisForm.classList.add("moarTLSUnsecure"); 94 | } 95 | } 96 | } 97 | 98 | { 99 | const lnks = document.querySelectorAll("a[href]"); 100 | for (let i = 0; i < lnks.length; i++) { 101 | const thisLink = lnks[i]; 102 | let sUnwrapped; 103 | if (thisLink.getAttribute("href")[0] === "#") { 104 | // Some funky page framework leaves all hrefs at # 105 | // and stores the true URL in a |data-url| attribute. 106 | if (thisLink.getAttribute("href").length === 1 && 107 | thisLink.getAttribute("data-url")) { 108 | sUnwrapped = unwrapUri(thisLink.getAttribute("data-url")); 109 | } 110 | else { 111 | continue; // Not a cross-page 'link' 112 | } 113 | } 114 | else { 115 | sUnwrapped = unwrapUri(thisLink.href); 116 | } 117 | 118 | cLinks++; 119 | if (isNonsecure(sUnwrapped)) { 120 | arrUnsecure.push(sUnwrapped); 121 | const oUnwrapped = document.createElement("a"); 122 | oUnwrapped.href = sUnwrapped; 123 | thisLink.title = oUnwrapped.protocol + "//" + oUnwrapped.hostname; 124 | thisLink.classList.add("moarTLSUnsecure"); 125 | } 126 | } 127 | } 128 | 129 | // We always need to send a report or else popup.js 130 | // can't know when analysis is complete. 131 | const obj = {LinkCount: cLinks, NonSecureImages: arrNonSecureImages.map(o=>{return o.src}), unsecure: arrUnsecure }; 132 | chrome.runtime.sendMessage(obj); 133 | } 134 | -------------------------------------------------------------------------------- /manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "manifest_version": 3, 3 | "name": "moarTLS Analyzer", 4 | "short_name": "moarTLS", 5 | "description": "Analyze webpages for non-secure link references.", 6 | "version": "1.1.0.1", 7 | "author": "Eric Lawrence (@ericlaw)", 8 | "options_ui": { 9 | "page": "options.html" 10 | }, 11 | 12 | "minimum_chrome_version": "120", 13 | 14 | "commands": { 15 | "_execute_action": { 16 | "description": "Check the current page for non-secure references", 17 | "suggested_key": { 18 | "default": "Alt+M" 19 | } 20 | } 21 | }, 22 | 23 | "icons": { 24 | "16": "images/icon16.png", 25 | "32": "images/icon32.png", 26 | "48": "images/icon48.png", 27 | "128": "images/icon128.png" 28 | }, 29 | 30 | "action": { 31 | "default_icon": { 32 | "19": "images/icon19.png", 33 | "38": "images/icon38.png" 34 | }, 35 | "default_popup": "popup.html", 36 | "default_title": "Mark non-secure links" 37 | }, 38 | 39 | "background": { 40 | "service_worker": "background.js" 41 | }, 42 | 43 | "host_permissions": ["https://*/*"], 44 | 45 | "permissions": [ 46 | "activeTab", 47 | "notifications", 48 | "storage", 49 | "scripting", 50 | "downloads" 51 | ] 52 | } 53 | -------------------------------------------------------------------------------- /options.css: -------------------------------------------------------------------------------- 1 | body { padding: 5px; } 2 | a { text-decoration: none; } 3 | label { 4 | -webkit-user-select: none; 5 | cursor: pointer; 6 | padding-bottom: 7px; 7 | padding-top: 7px; 8 | } 9 | input[type=checkbox] { 10 | margin-right:5px; 11 | } -------------------------------------------------------------------------------- /options.html: -------------------------------------------------------------------------------- 1 | 2 |
3 | 4 |12 |
16 | 17 | 18 |22 | In the report, Alt+Click non-secure links to check whether HTTPS is available. 23 |
24 | -------------------------------------------------------------------------------- /options.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | document.addEventListener('DOMContentLoaded', function() { 4 | const storage = (chrome.storage.sync ? chrome.storage.sync : chrome.storage.local); 5 | storage.get(null, function(prefs) 6 | { 7 | document.getElementById("cbRotateImages").checked = !(prefs && (false === prefs["bRotateNonSecureImages"])); 8 | document.getElementById("cbWarnOnNonSecureDownloads").checked = (prefs && (true === prefs["bWarnOnNonSecureDownloads"])); 9 | document.getElementById("cbWarnOnNonSecureDocument").checked = !(prefs && (false === prefs["bWarnOnNonSecureDocument"])); 10 | }); 11 | 12 | var checkboxes = document.querySelectorAll("input[type=checkbox]"); 13 | for (let i=0; i