├── .gitignore ├── src ├── options.css ├── icon16.png ├── icon48.png ├── icon128.png ├── options.html ├── manifest.json ├── options.js └── background.js ├── Makefile ├── README.md ├── LICENSE └── icon.svg /.gitignore: -------------------------------------------------------------------------------- 1 | dist/ 2 | -------------------------------------------------------------------------------- /src/options.css: -------------------------------------------------------------------------------- 1 | #config { 2 | width: 40em; 3 | height: 40em; 4 | } 5 | -------------------------------------------------------------------------------- /src/icon16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Rufflewind/chrome_cspmod/HEAD/src/icon16.png -------------------------------------------------------------------------------- /src/icon48.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Rufflewind/chrome_cspmod/HEAD/src/icon48.png -------------------------------------------------------------------------------- /src/icon128.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Rufflewind/chrome_cspmod/HEAD/src/icon128.png -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | build: 2 | mkdir -p dist 3 | rm -f dist/dist.zip 4 | zip dist/dist.zip LICENSE 5 | cd src && zip -r ../dist/dist.zip * 6 | 7 | clean: 8 | rm -f dist/dist.zip 9 | rmdir 2>/dev/null dist || true 10 | -------------------------------------------------------------------------------- /src/options.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Options 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /src/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "manifest_version": 2, 3 | "version": "1.2.0", 4 | "name": "Content Security Policy Override", 5 | "description": "Modify the Content Security Policy of web pages.", 6 | "icons": { 7 | "16": "icon16.png", 8 | "48": "icon48.png", 9 | "128": "icon128.png" 10 | }, 11 | "permissions": [ 12 | "storage", 13 | "webRequest", 14 | "webRequestBlocking", 15 | "*://*/" 16 | ], 17 | "options_ui": { 18 | "page": "options.html", 19 | "chrome_style": true 20 | }, 21 | "background": { 22 | "scripts": ["background.js"], 23 | "persistent": true 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Content Security Policy Override 2 | ================================ 3 | 4 | This is a simple extension that allows the user to modify the Content Security 5 | Policy (CSP) of web pages. 6 | 7 | Warning: improper use of this extension can diminish the security of your 8 | browser. Do not use unless you really know what you’re doing. 9 | 10 | Installation 11 | --------------------- 12 | 13 | You can install directly from the [Google Chrome webstore][1]. 14 | 15 | Usage 16 | ----- 17 | 18 | To edit the configuration, go to `chrome://extensions` and click *Options* 19 | under *Content Security Policy Override*. 20 | 21 | The text area in the *Options* automatically saves as you edit. 22 | 23 | [1]: https://chrome.google.com/webstore/detail/lhieoncdgamiiogcllfmboilhgoknmpi 24 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | License of this extension 2 | ========================= 3 | 4 | Copyright (c) 2014-2015 Phil Ruffwind 5 | 6 | Permission is hereby granted, free of charge, to any person obtaining a copy 7 | of this software and associated documentation files (the "Software"), to deal 8 | in the Software without restriction, including without limitation the rights 9 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | copies of the Software, and to permit persons to whom the Software is 11 | furnished to do so, subject to the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be included in all 14 | copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | SOFTWARE. 23 | -------------------------------------------------------------------------------- /src/options.js: -------------------------------------------------------------------------------- 1 | function restore() { 2 | chrome.runtime.sendMessage({ 3 | action: "RESTORE_CONFIG" 4 | }, function(response) { 5 | document.getElementById("config").value = response; 6 | }); 7 | } 8 | 9 | function save() { 10 | chrome.runtime.sendMessage({ 11 | action: "SAVE_CONFIG", 12 | config: document.getElementById("config").value 13 | }, function(response) { 14 | var color; 15 | if (response === "SUCCESS") { 16 | color = ""; 17 | } else { 18 | color = "#ffbbbb"; 19 | } 20 | document.getElementById("config").style.backgroundColor = color; 21 | }); 22 | } 23 | 24 | function throttle(func, delay) { 25 | var timeoutID = null; 26 | function wrappedFunc() { 27 | timeoutID = null; 28 | func(); 29 | } 30 | return function() { 31 | if (timeoutID !== null) { 32 | window.clearTimeout(timeoutID); 33 | } 34 | timeoutID = window.setTimeout(wrappedFunc, delay); 35 | }; 36 | } 37 | 38 | var throttledSave = throttle(save, 250); 39 | document.addEventListener("DOMContentLoaded", restore); 40 | document.getElementById("config").addEventListener("input", throttledSave); 41 | -------------------------------------------------------------------------------- /icon.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 21 | 23 | 41 | 43 | 44 | 46 | image/svg+xml 47 | 49 | 50 | 51 | 52 | 53 | 59 | 62 | 69 | 76 | 83 | 84 | 85 | 89 | 99 | 109 | C 121 | 122 | 123 | -------------------------------------------------------------------------------- /src/background.js: -------------------------------------------------------------------------------- 1 | function dropCommentsAndWhitespace(s) { 2 | var r = ""; 3 | var lines = s.match(/[^\r\n]+/g) || []; 4 | lines.forEach(function(line) { 5 | if (line.match(/^\s*#/) !== null || 6 | line.match(/^\s*$/) !== null) { 7 | return; 8 | } 9 | r += line + "\n"; 10 | }); 11 | return r; 12 | } 13 | 14 | function parseRules(config) { 15 | config = dropCommentsAndWhitespace(config); 16 | if (config === "") { 17 | return []; 18 | } 19 | try { 20 | return JSON.parse(config); 21 | } catch (_) { 22 | return null; 23 | } 24 | } 25 | 26 | function validateRules(rules) { 27 | if (!Array.isArray(rules)) { 28 | return null; 29 | } 30 | var fail = false; 31 | rules.forEach(function(rule) { 32 | if (rule.length !== 2 || 33 | typeof rule[0] !== "string" || 34 | !Array.isArray(rule[1])) { 35 | fail = true; 36 | return null; 37 | } 38 | rule[1].forEach(function(subrule) { 39 | if (subrule.length !== 2 || 40 | typeof subrule[0] !== "string" || 41 | typeof subrule[1] !== "string") { 42 | fail = true; 43 | return null; 44 | } 45 | }); 46 | if (fail) { 47 | return null; 48 | } 49 | }); 50 | if (fail) { 51 | return null; 52 | } 53 | return rules; 54 | } 55 | 56 | function regexpifyRules(newRules) { 57 | if (newRules === null) { 58 | return null; 59 | } 60 | return newRules.map(function(rule) { 61 | return [ 62 | new RegExp(rule[0]), 63 | rule[1].map(function(subrule) { 64 | return [ 65 | new RegExp(subrule[0]), 66 | subrule[1] 67 | ]; 68 | }) 69 | ]; 70 | }); 71 | } 72 | 73 | function processConfig(config) { 74 | if (typeof config !== "string") { 75 | config = ""; 76 | } 77 | return regexpifyRules(validateRules(parseRules(config))); 78 | } 79 | 80 | function messageHandler(request, sender, sendResponse) { 81 | if (request.action === "RESTORE_CONFIG") { 82 | chrome.storage.sync.get({config: defaultConfig}, function(items) { 83 | var config = items.config; 84 | if (typeof config !== "string") { 85 | config = defaultConfig; 86 | } 87 | sendResponse(config); 88 | }); 89 | return true; 90 | } else if (request.action === "SAVE_CONFIG") { 91 | var config = request.config; 92 | newRules = processConfig(config); 93 | if (newRules !== null) { 94 | chrome.storage.sync.set({config: config}); 95 | rules = newRules; 96 | sendResponse("SUCCESS"); 97 | } else { 98 | sendResponse("FAILURE"); 99 | } 100 | } else { 101 | console.error("Invalid request: ", request); 102 | } 103 | } 104 | 105 | function requestProcessor(details) { 106 | for (var i = 0, iLen = rules.length; i !== iLen; ++i) { 107 | if (!rules[i][0].test(details.url)) { 108 | continue; 109 | } 110 | var subrules = rules[i][1]; 111 | var headers = details.responseHeaders; 112 | for (var j = 0, jLen = headers.length; j !== jLen; ++j) { 113 | var header = headers[j]; 114 | var name = header.name.toLowerCase(); 115 | if (name !== "content-security-policy" && 116 | name !== "content-security-policy-report-only" && 117 | name !== "x-webkit-csp") { 118 | continue; 119 | } 120 | for (var k = 0, kLen = subrules.length; k !== kLen; ++k) { 121 | header.value = header.value.replace(subrules[k][0], 122 | subrules[k][1]); 123 | } 124 | } 125 | return {responseHeaders: headers}; 126 | } 127 | } 128 | 129 | var defaultConfig = 130 | "# Rules need to be in JSON syntax:\n" + 131 | "#\n" + 132 | "# [\n" + 133 | '# ["url-regexp", [\n' + 134 | '# ["pattern-regexp", "replacement-string"],\n' + 135 | "# ...\n" + 136 | "# ]],\n" + 137 | "# ...\n" + 138 | "# ]\n" + 139 | "#\n" + 140 | "# Keep in mind that JSON does not allow trailing commas.\n" + 141 | "# Lines starting with '#' are ignored. Have fun!\n" + 142 | "\n" + 143 | "[\n" + 144 | "# Example: whitelisting MathJax on GitHub:\n" + 145 | '# ["https://gist\\\\.github\\\\.com", [\n' + 146 | '# ["script-src", "script-src https://cdn.mathjax.org"],\n' + 147 | '# ["font-src", "font-src https://cdn.mathjax.org"]\n' + 148 | "# ]]\n" + 149 | "]\n"; 150 | 151 | var rules = [] 152 | 153 | chrome.storage.sync.get({config: ""}, function(items) { 154 | newRules = processConfig(items.config); 155 | if (newRules !== null) { 156 | rules = newRules; 157 | } 158 | }); 159 | chrome.runtime.onMessage.addListener(messageHandler); 160 | chrome.webRequest.onHeadersReceived.addListener(requestProcessor, { 161 | urls: ["*://*/*"], 162 | types: ["main_frame", "sub_frame"] 163 | }, ["blocking", "responseHeaders"]); 164 | --------------------------------------------------------------------------------