From 803826ae9e4ac65bee920740f8c89e20bc90f7da Mon Sep 17 00:00:00 2001 From: Wrongthink Date: Sat, 29 Apr 2023 08:50:32 -0400 Subject: [PATCH] Initial upload --- LICENSE | 39 ++++++++ Makefile | 2 + README.md | 43 +++++++++ icon.svg | 14 +++ manifest.json | 42 +++++++++ screenshot.png | Bin 0 -> 45450 bytes src/bg.js | 225 ++++++++++++++++++++++++++++++++++++++++++++++ src/content.js | 17 ++++ src/popup.css | 69 ++++++++++++++ src/popup.html | 31 +++++++ src/popup.js | 179 ++++++++++++++++++++++++++++++++++++ src/settings.css | 29 ++++++ src/settings.html | 17 ++++ src/settings.js | 22 +++++ src/shared.js | 35 ++++++++ 15 files changed, 764 insertions(+) create mode 100644 LICENSE create mode 100644 Makefile create mode 100644 README.md create mode 100644 icon.svg create mode 100644 manifest.json create mode 100644 screenshot.png create mode 100644 src/bg.js create mode 100644 src/content.js create mode 100644 src/popup.css create mode 100644 src/popup.html create mode 100644 src/popup.js create mode 100644 src/settings.css create mode 100644 src/settings.html create mode 100644 src/settings.js create mode 100644 src/shared.js diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..814ef90 --- /dev/null +++ b/LICENSE @@ -0,0 +1,39 @@ +Copyright (c) 2007 GPL Project + +This file is free software: you may copy, redistribute and/or modify it +under the terms of the GNU General Public License as published by the +Free Software Foundation, either version 2 of the License, or (at your +option) any later version. + +This file is distributed in the hope that it will be useful, but +WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program. If not, see . + +This file incorporates work covered by the following copyright and +permission notice: + +MIT License + +Copyright (c) 2022 Tobias Bengfort + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..7b6331f --- /dev/null +++ b/Makefile @@ -0,0 +1,2 @@ +bundle.zip: manifest.json icon.svg src/* + zip $@ $^ diff --git a/README.md b/README.md new file mode 100644 index 0000000..1f8e4fa --- /dev/null +++ b/README.md @@ -0,0 +1,43 @@ +paraMatrix - block requests based on domain and request type + +Built atop the excellent project [xiMatrix](https://github.com/xi/xiMatrix) by Tobias Bengfort, itself influenced by Raymond Hill's [uMatrix](https://github.com/gorhill/uMatrix). paraMatrix blocks web requests via default-deny global default which can then whitelist elements on a per-site basis. For those who demand nothing less than granular control. + +![screenshot](screenshot.png) + +*~ "para- word-forming element of Latin origin meaning "defense, protection against; that which protects from."* + +## Why use xiMatrix as a basis instead of uMatrix? + +xiMatrix is simpler with a smaller codebase and is therefore more suitable to hack upon. It is already built in the spririt of 'do one thing and do it well' and implements some of the features I would have liked to see added to uMatrix. uMatrix covers things which may be considered outside the scope of a web request firewall such as spoofing values and managing local storage. + +## Why not just contribute to xi's xiMatrix? + +He has clearly stated in his project that it is a personal tool and will not likely be extended further. I will, however, try to track changes to xiMatrix to incorprate into paraMatrix. Although it may eventually be spun off as its own fork. + +## In comparison to xiMatrix + +- Open to being extended +- Separate image and media columns +- Reload page button in-panel +- Dark theme! +- Dialog clarifications +- More to come + +## In comparison to uMatrix + +- Actively maintained +- Smaller, simpler code +- Rules encoded as JSON +- Rows for managing inline scripts, styles, and images +- Keyboard navigation +- A column for blocking fonts + +## TO DO + +- Add column for blocking cookies. Cookie blocking is not as simple as other requests, as it necessitates accepting site cookies (but *not returning* them) when they are blocked. +- Display enumerated requests blocked in icon. +- Placeholder element for blocked images/video. +- Update in-place while keeping paraMatrix popup open. +- Explicit deny option for cells. +- Remove setting to disable request enumeration (performance impact is negligible). +- Migrate help dialog to dedicated docs page. diff --git a/icon.svg b/icon.svg new file mode 100644 index 0000000..5dc2b93 --- /dev/null +++ b/icon.svg @@ -0,0 +1,14 @@ + + + + + + + + + + + + + + diff --git a/manifest.json b/manifest.json new file mode 100644 index 0000000..189b4a3 --- /dev/null +++ b/manifest.json @@ -0,0 +1,42 @@ +{ + "manifest_version": 2, + "name": "paraMatrix", + "author": "Wrongthink", + "homepage_url": "https://gitler.moe/Wrongthink/paraMatrix", + "description": "block requests based on domain and type", + "version": "🐢.🐎", + "browser_action": { + "default_title": "paraMatrix", + "default_popup": "src/popup.html", + "default_icon": "icon.svg" + }, + "icons": { + "48": "icon.svg" + }, + "background": { + "scripts": ["src/shared.js", "src/bg.js"], + "persistent": false + }, + "content_scripts": [{ + "js": ["src/content.js"], + "matches": [""], + "run_at": "document_start" + }], + "options_ui": { + "page": "src/settings.html", + "open_in_tab": true + }, + "permissions": [ + "storage", + "tabs", + "webNavigation", + "webRequest", + "webRequestBlocking", + "" + ], + "browser_specific_settings": { + "gecko": { + "id": "{936cea12-8e61-4929-b589-caece971bbd7}" + } + } +} diff --git a/screenshot.png b/screenshot.png new file mode 100644 index 0000000000000000000000000000000000000000..b49b42095f14cde069c30c121956e66ec83839cf GIT binary patch literal 45450 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 E0ieEuf&c&j literal 0 HcmV?d00001 diff --git a/src/bg.js b/src/bg.js new file mode 100644 index 0000000..2a18bca --- /dev/null +++ b/src/bg.js @@ -0,0 +1,225 @@ +/* global browser shared */ + +var lock = Promise.resolve(); + +var STORAGE_DEFAULTS = { + 'rules': {}, + 'savedRules': {}, + 'requests': {}, + 'recording': true, +}; + +var getHostname = function(url) { + var u = new URL(url); + return u.hostname; +}; + +var storageGet = function(key) { + return browser.storage.local.get(key).then(data => { + return data[key] ?? STORAGE_DEFAULTS[key]; + }); +}; + +var _storageChange = function(key, fn) { + return storageGet(key).then(oldValue => { + var data = {}; + data[key] = fn(oldValue); + return browser.storage.local.set(data); + }); +}; + +var storageChange = function(key, fn) { + lock = lock.then(() => _storageChange(key, fn)); + return lock; +}; + +var setRule = function(context, hostname, type, rule) { + return storageGet('savedRules').then(savedRules => { + return storageChange('rules', rules => { + if (hostname === 'first-party') { + context = '*'; + } + if (!rules[context]) { + rules[context] = savedRules[context] || {}; + } + if (!rules[context][hostname]) { + rules[context][hostname] = {}; + } + if (rule) { + rules[context][hostname][type] = rule; + } else { + delete rules[context][hostname][type]; + if (Object.keys(rules[context][hostname]).length === 0) { + delete rules[context][hostname]; + } + if (Object.keys(rules[context]).length === 0 && !savedRules[context]) { + delete rules[context]; + } + } + return rules; + }); + }); +}; + +var getRules = function(context) { + return Promise.all([ + storageGet('rules'), + storageGet('savedRules'), + ]).then(([rules, savedRules]) => { + var restricted = {}; + restricted['*'] = rules['*'] || savedRules['*'] || {}; + restricted[context] = rules[context] || savedRules[context] || {}; + restricted.dirty = !!rules[context]; + return restricted; + }); +}; + +var pushRequest = function(tabId, hostname, type) { + return storageGet('recording').then(recording => { + if (recording) { + return storageChange('requests', requests => { + if (!requests[tabId]) { + requests[tabId] = {}; + } + if (!requests[tabId][hostname]) { + requests[tabId][hostname] = {}; + } + if (!requests[tabId][hostname][type]) { + requests[tabId][hostname][type] = 0; + } + requests[tabId][hostname][type] += 1; + return requests; + }); + } + }); +}; + +var clearRequests = function(tabId) { + return storageChange('requests', requests => { + if (requests[tabId]) { + delete requests[tabId]; + } + return requests; + }); +}; + +var getCurrentTab = function() { + return browser.tabs.query({ + active: true, + currentWindow: true, + }).then(tabs => tabs[0]); +}; + +browser.runtime.onMessage.addListener((msg, sender) => { + if (msg.type === 'get') { + return getCurrentTab().then(tab => { + var context = getHostname(tab.url); + return Promise.all([ + getRules(context), + storageGet('requests'), + storageGet('recording'), + ]).then(([rules, requests, recording]) => { + return { + context: context, + rules: rules, + requests: requests[tab.id] || {}, + recording: recording, + }; + }); + }); + } else if (msg.type === 'setRule') { + return setRule( + msg.data.context, + msg.data.hostname, + msg.data.type, + msg.data.value, + ).then(() => getRules(msg.data.context)); + } else if (msg.type === 'commit') { + var r; + return storageChange('rules', rules => { + r = rules[msg.data]; + delete rules[msg.data]; + return rules; + }).then(() => storageChange('savedRules', savedRules => { + if (Object.keys(r).length === 0) { + delete savedRules[msg.data]; + } else { + savedRules[msg.data] = r; + } + return savedRules; + })); + } else if (msg.type === 'securitypolicyviolation') { + return pushRequest(sender.tab.id, 'inline', msg.data); + } else if (msg.type === 'toggleRecording') { + return storageChange('recording', recording => !recording); + } +}); + +browser.tabs.onRemoved.addListener(clearRequests); +browser.webNavigation.onBeforeNavigate.addListener(details => { + if (details.frameId === 0) { + return clearRequests(details.tabId); + } +}); + +browser.webRequest.onBeforeRequest.addListener(details => { + if (details.type === 'main_frame') { + return; + } + + var context = getHostname(details.documentUrl); + if (details.frameAncestors.length) { + var last = details.frameAncestors.length - 1; + context = getHostname(details.frameAncestors[last].url); + } + var hostname = getHostname(details.url); + var type = shared.TYPE_MAP[details.type] || 'other'; + + return Promise.all([ + pushRequest(details.tabId, hostname, type), + getRules(context), + ]).then(([_, rules]) => { + if (!shared.shouldAllow(rules, context, hostname, type)) { + if (details.type === 'sub_frame') { + // this can in turn be blocked by a local CSP + return {redirectUrl: 'data:,' + encodeURIComponent(details.url)}; + } else { + return {cancel: true}; + } + } + }); +}, {urls: ['']}, ['blocking']); + +browser.webRequest.onHeadersReceived.addListener(function(details) { + var context = getHostname(details.url); + return Promise.all([ + getRules(context), + storageGet('recording'), + ]).then(([rules, recording]) => { + var csp = (type, value) => { + var name = 'Content-Security-Policy'; + if (shared.shouldAllow(rules, context, 'inline', type)) { + if (recording) { + name = 'Content-Security-Policy-Report-Only'; + } else { + return; + } + } + details.responseHeaders.push({ + name: name, + value: value, + }); + }; + + csp('css', "style-src 'self' *"); + csp('script', "script-src 'self' *"); + csp('media', "img-src 'self' *"); + + return { + responseHeaders: details.responseHeaders, + }; + }); +}, { + urls: [''], + types: ['main_frame'], +}, ['blocking', 'responseHeaders']); diff --git a/src/content.js b/src/content.js new file mode 100644 index 0000000..f605ef2 --- /dev/null +++ b/src/content.js @@ -0,0 +1,17 @@ +/* global browser */ + +const TYPE_MAP = { + 'style-src': 'css', + 'script-src': 'script', + 'img-src': 'media', +}; + +document.addEventListener('securitypolicyviolation', event => { + var type = TYPE_MAP[event.violatedDirective]; + if (type) { + browser.runtime.sendMessage({ + type: 'securitypolicyviolation', + data: type, + }); + } +}); diff --git a/src/popup.css b/src/popup.css new file mode 100644 index 0000000..b25549f --- /dev/null +++ b/src/popup.css @@ -0,0 +1,69 @@ +:root { + --blue-light: #5e69f0; + --blue-dark: #2425ac; + --orange-light: #3f372f; + --dark-grey: #1b1b1b; + --text-light: #838383; + --text-dark: #fff; +} + +.toolbar { + display: flex; + gap: 0.5em; +} +.toolbar label { + flex-grow: 1; +} + +table { + background: var(--orange-light); + border-spacing: 0; + margin-bottom: 0.2em; +} +th, td { + position: relative; + border: 1px solid #1f2042; + min-width: 3.4em; + line-height: 1.8; + text-align: center; + font-weight: normal; +} +th:first-child { + text-align: right; +} +td.disabled { + background-color: var(--dark-grey); +} + +table input { + appearance: none; + position: absolute; + top: 0; + bottom: 0; + left: 0; + right: 0; + margin: 0; + cursor: pointer; +} +table input:focus-visible { + outline: 2px solid; + z-index: 1; +} +.inherit-allow { + background: var(--blue-light); +} +.inherit-allow ~ span { + color: var(--text-dark); +} +table input:checked { + background: var(--blue-dark); +} +table input ~ span { + pointer-events: none; + position: relative; + z-index: 1; + color: var(--text-light); +} +table input:checked ~ span { + color: var(--text-dark); +} diff --git a/src/popup.html b/src/popup.html new file mode 100644 index 0000000..117ad50 --- /dev/null +++ b/src/popup.html @@ -0,0 +1,31 @@ + + + + + + + +
+

+ + + + +
+
+ Help +

Columns represent different types of requests. Rows represent domains. Numbers (if recording is enabled) show how many requests of a given type the current tab tries to make to the given domain. Grey cells are blocked, blue cells are allowed. Light blue cells inherit rules indirectly because they represent a sub-domain of an allowed domain. Black cells are disabled.

+

Everything is blocked by default. Click on a cell to allow it, then click reload page to load those assets. Click commit to save the configuration for the current site. Click on the domain or type to allow an entire row or column. Master rows are available for:

+
    +
  • inline: Controls <script>-elements that are directly embedded in the HTML code. Categories that cannot be inline are disabled.
  • +
  • first-party: Sets global defaults for requests to the same domain as the page itself.
  • +
  • sub-domains: If a domain is allowed, all of its subdomains inhereit that rule.
  • +
+
+ + + + diff --git a/src/popup.js b/src/popup.js new file mode 100644 index 0000000..96bba3d --- /dev/null +++ b/src/popup.js @@ -0,0 +1,179 @@ +/* global browser shared */ + +var context; +var requests; +var rules; + +var table = document.querySelector('table'); +var recording = document.querySelector('[name="recording"]') +var commitButton = document.querySelector('[name="commit"]'); +var reloadButton = document.querySelector('[name="reload"]'); + +var sendMessage = function(type, data) { + return browser.runtime.sendMessage({type: type, data: data}); +}; + +var getHostnames = function() { + var hostnames = []; + + var addSubdomains = function(h) { + if (['inline', 'first-party', '*'].includes(h)) { + return; + } + hostnames.unshift(h); + var parts = h.split('.'); + while (parts.length > 2) { + parts.shift(); + hostnames.unshift(parts.join('.')); + } + }; + + for (const hostname in rules[context]) { + addSubdomains(hostname); + } + for (const hostname in requests) { + addSubdomains(hostname); + } + + addSubdomains(context); + + var contextRoot = context.split('.').slice(-2).join('.'); + hostnames = hostnames + .map(h => { + var parts = h.split('.'); + var root = parts.slice(-2).join('.'); + var isContext = root === contextRoot ? 0 : 1; + return [isContext, parts.reverse()]; + }) + .sort() + .map(a => a[1].reverse().join('.')); + + return hostnames.filter((value, i) => hostnames.indexOf(value) === i); +}; + +var updateInherit = function(type) { + var selector = 'input'; + if (type !== '*') { + selector += `[data-type="${type}"]`; + } + table.querySelectorAll(selector).forEach(input => { + input.classList.toggle('inherit-allow', shared.shouldAllow( + rules, + context, + input.dataset.hostname, + input.dataset.type, + )); + }); +}; + +var createCheckbox = function(hostname, type) { + var input = document.createElement('input'); + input.type = 'checkbox'; + input.dataset.hostname = hostname; + input.dataset.type = type; + + var c = (hostname === 'first-party') ? '*' : context; + input.checked = (rules[c][hostname] || {})[type]; + + input.onchange = () => { + sendMessage('setRule', { + context: context, + hostname: hostname, + type: type, + value: input.checked, + }).then(newRules => { + rules = newRules; + commitButton.disabled = !rules.dirty; + reloadButton.disabled = !rules.dirty; + updateInherit(type); + }); + }; + + return input; +}; + +var createCell = function(tag, hostname, type, text) { + const cell = document.createElement(tag); + cell.append(createCheckbox(hostname, type)); + + const span = document.createElement('span'); + span.textContent = text; + cell.append(span); + + return cell; +}; + +var createHeader = function() { + var tr = document.createElement('tr'); + + var th = document.createElement('th'); + th.textContent = context; + tr.append(th); + + for (const type of shared.TYPES) { + tr.append(createCell('th', '*', type, type)); + } + return tr; +}; + +var createRow = function(hostname) { + var tr = document.createElement('tr'); + tr.append(createCell('th', hostname, '*', hostname)); + for (const type of shared.TYPES) { + const count = (requests[hostname] || {})[type]; + + if (hostname !== 'inline' || ['css', 'script', 'image', 'media'].includes(type)) { + tr.append(createCell('td', hostname, type, count)); + } else { + const td = document.createElement('td'); + td.className = 'disabled'; + tr.append(td); + } + } + return tr; +}; + +var loadContext = function() { + sendMessage('get').then(data => { + context = data.context; + requests = data.requests; + rules = data.rules; + recording.checked = data.recording; + commitButton.disabled = !rules.dirty; + + table.innerHTML = ''; + table.append(createHeader()); + table.append(createRow('inline')); + table.append(createRow('first-party')); + + for (const hostname of getHostnames()) { + table.append(createRow(hostname)); + } + + updateInherit('*'); + }); +}; + +browser.webNavigation.onBeforeNavigate.addListener(window.close); + +document.querySelector('[name="settings"]').addEventListener('click', event => { + browser.runtime.openOptionsPage(); +}); + +document.addEventListener('DOMContentLoaded', () => { + loadContext(); +}); + +recording.addEventListener('change', event => { + sendMessage('toggleRecording'); +}); + +reloadButton.addEventListener('click', event => { + browser.tabs.reload({bypassCache: true}); +}); + +commitButton.addEventListener('click', event => { + sendMessage('commit', context).then(() => { + commitButton.disabled = true; + }); +}); diff --git a/src/settings.css b/src/settings.css new file mode 100644 index 0000000..e24c787 --- /dev/null +++ b/src/settings.css @@ -0,0 +1,29 @@ +* { + box-sizing: border-box; +} + +html, +body { + margin: 0; + padding: 0; +} + +form { + height: 100vh; + display: grid; + grid-template-rows: 1fr min-content; + grid-template-columns: 1fr 1fr; + grid-gap: 0.5em; + padding: 0.5em; +} + +textarea { + width: 100%; + resize: none; +} + +button { + padding: 0.5em 2em; + justify-self: end; + grid-column: 1 / 3; +} diff --git a/src/settings.html b/src/settings.html new file mode 100644 index 0000000..fe1a854 --- /dev/null +++ b/src/settings.html @@ -0,0 +1,17 @@ + + + + + + paraMatrix — edit rules + + +
+ + + +
+ + + + diff --git a/src/settings.js b/src/settings.js new file mode 100644 index 0000000..644d5bd --- /dev/null +++ b/src/settings.js @@ -0,0 +1,22 @@ +var form = document.querySelector('form'); +var textarea1 = document.querySelector('textarea.rules'); +var textarea2 = document.querySelector('textarea.savedRules'); + +browser.storage.local.get(['rules', 'savedRules']).then(data => { + var rules = data.rules || {}; + var savedRules = data.savedRules || {}; + textarea1.value = JSON.stringify(rules, null, 2) + textarea2.value = JSON.stringify(savedRules, null, 2) +}); + +form.addEventListener('submit', event => { + event.preventDefault(); + var rules = JSON.parse(textarea1.value); + var savedRules = JSON.parse(textarea2.value); + browser.storage.local.set({ + 'rules': rules, + 'savedRules': savedRules, + }).then(() => { + location.reload(); + }); +}); diff --git a/src/shared.js b/src/shared.js new file mode 100644 index 0000000..5bc8012 --- /dev/null +++ b/src/shared.js @@ -0,0 +1,35 @@ +var shared = {}; + +shared.TYPES = ['font', 'css', 'image', 'media', 'script', 'xhr', 'frame', 'other']; +shared.TYPE_MAP = { + 'stylesheet': 'css', + 'font': 'font', + 'image': 'image', + 'imageset': 'image', + 'media': 'media', + 'script': 'script', + 'beacon': 'xhr', + 'xmlhttprequest': 'xhr', + 'websocket': 'xhr', + 'sub_frame': 'frame', +}; + +shared.shouldAllow = function(rules, context, hostname, type) { + var hostnames = ['*', hostname]; + var parts = hostname.split('.'); + while (parts.length > 2) { + parts.shift(); + hostnames.push(parts.join('.')); + } + if (context !== '*' && hostnames.some(h => h === context)) { + hostnames.push('first-party'); + } + + return [context, '*'].some(c => { + return rules[c] && hostnames.some(h => { + return rules[c][h] && [type, '*'].some(t => { + return !!rules[c][h][t]; + }); + }); + }); +};