Improve CSP clobbering (commit lost last time)

- Get csp setting asynchronously
 - Case insensitively match content-security-policy header value
 - Parse csp correctly
 - Simplify code

(When I rebased last time I lost the content of this commit somehow...)
This commit is contained in:
Colin Caine 2018-05-17 21:38:22 +01:00
parent d28fc7f0f6
commit 8f67be2d17
4 changed files with 75 additions and 42 deletions

6
package-lock.json generated
View file

@ -2011,6 +2011,10 @@
"integrity": "sha1-ojD2T1aDEOFJgAmUB5DsmVRbyn4=", "integrity": "sha1-ojD2T1aDEOFJgAmUB5DsmVRbyn4=",
"dev": true "dev": true
}, },
"csp-serdes": {
"version": "github:cmcaine/csp-serdes#6c6fe34dbd138855e5c26f331b094e9a75358a64",
"from": "github:cmcaine/csp-serdes"
},
"css": { "css": {
"version": "2.2.1", "version": "2.2.1",
"resolved": "https://registry.npmjs.org/css/-/css-2.2.1.tgz", "resolved": "https://registry.npmjs.org/css/-/css-2.2.1.tgz",
@ -9376,7 +9380,7 @@
} }
}, },
"web-ext-types": { "web-ext-types": {
"version": "github:kelseasy/web-ext-types#5f6e888318984c185413f8943d2616915e2af88f", "version": "github:kelseasy/web-ext-types#53d82dcea599d34a23b5f4aa6a9c616613c7adc2",
"from": "github:kelseasy/web-ext-types", "from": "github:kelseasy/web-ext-types",
"dev": true "dev": true
}, },

View file

@ -5,6 +5,7 @@
"dependencies": { "dependencies": {
"@types/css": "0.0.31", "@types/css": "0.0.31",
"@types/nearley": "^2.11.0", "@types/nearley": "^2.11.0",
"csp-serdes": "github:cmcaine/csp-serdes",
"css": "^2.2.1", "css": "^2.2.1",
"fuse.js": "^3.2.0", "fuse.js": "^3.2.0",
"mark.js": "^8.11.1", "mark.js": "^8.11.1",

View file

@ -55,28 +55,35 @@ import * as webext from "./lib/webext"
l: prom => prom.then(console.log).catch(console.error), l: prom => prom.then(console.log).catch(console.error),
}) })
// {{{ Clobber CSP
// This should be removed once https://bugzilla.mozilla.org/show_bug.cgi?id=1267027 is fixed // This should be removed once https://bugzilla.mozilla.org/show_bug.cgi?id=1267027 is fixed
let cspListener function addCSPListener() {
if (config.get("csp") == "clobber") { browser.webRequest.onHeadersReceived.addListener(
cspListener = browser.webRequest.onHeadersReceived.addListener( request.clobberCSP,
request.addurltocsp,
{ urls: ["<all_urls>"], types: ["main_frame"] }, { urls: ["<all_urls>"], types: ["main_frame"] },
["blocking", "responseHeaders"], ["blocking", "responseHeaders"],
) )
} }
function removeCSPListener() {
browser.webRequest.onHeadersReceived.removeListener(request.clobberCSP)
}
config.getAsync("csp").then(csp => csp === "clobber" && addCSPListener())
browser.storage.onChanged.addListener((changes, areaname) => { browser.storage.onChanged.addListener((changes, areaname) => {
if (config.get("csp") == "clobber") { if ("userconfig" in changes) {
cspListener = browser.webRequest.onHeadersReceived.addListener( if (changes.userconfig.newValue.csp === "clobber") {
request.addurltocsp, addCSPListener()
{ urls: ["<all_urls>"], types: ["main_frame"] }, } else {
["blocking", "responseHeaders"], removeCSPListener()
) }
} else {
// This doesn't work. :(
// browser.webRequest.onHeadersReceived.removeListener(cspListener)
} }
}) })
// }}}
// Prevent Tridactyl from being updated while it is running in the hope of fixing #290 // Prevent Tridactyl from being updated while it is running in the hope of fixing #290
browser.runtime.onUpdateAvailable.addListener(_ => {}) browser.runtime.onUpdateAvailable.addListener(_ => {})

View file

@ -1,32 +1,53 @@
import * as config from "./config" import * as config from "./config"
import * as csp from "csp-serdes"
export function addurltocsp(response) { class DefaultMap extends Map {
let headers = response["responseHeaders"] constructor(private defaultFactory, ...args) {
let cspind = headers.findIndex( super(...args)
header => header.name == "Content-Security-Policy", }
)
// if it's found get(key) {
if (cspind > -1) { let ans = super.get(key)
// Split the csp header up so we can manage it individually. if (ans === undefined) {
let csparr = [headers[cspind]["value"].split("; ")][0] ans = this.defaultFactory(key)
super.set(key, ans)
for (let i = 0; i < csparr.length; i++) { }
// Add 'unsafe-inline' as a directive since we use it return ans
if (csparr[i].indexOf("style-src") > -1) { }
if (csparr[i].indexOf("'self'") > -1) { }
csparr[i] = csparr[i].replace(
"'self'", /**
"'self' 'unsafe-inline'", * Reduce CSP safety to permit tridactyl to run correctly
) *
} * style-src needs 'unsafe-inline' (hinting styles) and 'self' (mode indicator hiding)
} * script-src needs 'unsafe-eval' (event hijacking)
// Remove the element if it's a sandbox directive * - but that's pretty dangerous, so maybe we shouldn't just clobber it?
if (csparr[i] === "sandbox") { * sandbox must not be set
csparr.splice(i, 1) *
} * This only needs to happen because of a Firefox bug and we should stop doing
} * it when they fix the bug.
// Join the header up after clobberin' */
headers[cspind]["value"] = csparr.join("; ") export function clobberCSP(response) {
const headers = response["responseHeaders"]
const cspHeader = headers.find(
header => header.name.toLowerCase() === "content-security-policy",
)
if (cspHeader !== undefined) {
const policy = new DefaultMap(
() => new Set(),
csp.parse(cspHeader.value),
)
policy.delete("sandbox")
policy
.get("style-src")
.add("'unsafe-inline'")
.add("'self'")
// policy.get("script-src").add("'unsafe-eval'")
// Replace old CSP
cspHeader.value = csp.serialize(policy)
return { responseHeaders: headers }
} else {
return {}
} }
return { responseHeaders: headers }
} }