aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorjahoti <jahoti@tilde.team>2021-09-06 00:00:00 +0000
committerjahoti <jahoti@tilde.team>2021-09-06 00:00:00 +0000
commit5dab077b9bb7564f2c556b197c5c416c41783112 (patch)
treed6f1230a4814c79b59147af474ce3e2683bb25ad
parent51d43685c667567516cfbda8dfeb75e98c00619f (diff)
downloadbrowser-extension-5dab077b9bb7564f2c556b197c5c416c41783112.tar.gz
browser-extension-5dab077b9bb7564f2c556b197c5c416c41783112.zip
Replace CSP filtering with blocking
CSP headers are now blocked completely rather than modified. Also, filtering is applied whenever a payload is injected.
-rw-r--r--background/policy_injector.js66
-rw-r--r--common/misc.js13
-rw-r--r--content/main.js19
3 files changed, 31 insertions, 67 deletions
diff --git a/background/policy_injector.js b/background/policy_injector.js
index 72318d4..318190b 100644
--- a/background/policy_injector.js
+++ b/background/policy_injector.js
@@ -11,54 +11,24 @@
* IMPORT sign_data
* IMPORT extract_signed
* IMPORT sanitize_csp_header
- * IMPORT csp_rule
+ * IMPORT make_csp_rule
* IMPORT is_csp_header_name
* IMPORTS_END
*/
function inject_csp_headers(headers, policy)
{
- let csp_headers;
- let old_signature;
- let hachette_header;
+ if (!policy.allow || policy.has_payload) {
+ /* Remove report-only CSP headers that snitch on us. */
+ headers = headers.filter(h => !is_csp_header_name(h.name, true));
- for (const header of headers.filter(h => h.name === "x-hachette")) {
- /* x-hachette header has format: <signature>_0_<data> */
- const match = /^([^_]+)_(0_.*)$/.exec(header.value);
- if (!match)
- continue;
-
- const result = extract_signed(...match.slice(1, 3));
- if (result.fail)
- continue;
-
- /* This should succeed - it's our self-produced valid JSON. */
- const old_data = JSON.parse(decodeURIComponent(result.data));
-
- /* Confirmed- it's the originals, smuggled in! */
- csp_headers = old_data.csp_headers;
- old_signature = old_data.policy_sig;
-
- hachette_header = header;
- break;
- }
-
- if (!hachette_header) {
- hachette_header = {name: "x-hachette"};
- headers.push(hachette_header);
+ /* Add our own CSP header */
+ headers.push({
+ name: "content-security-policy",
+ value: make_csp_rule(policy)
+ });
}
-
- csp_headers = csp_headers ||
- headers.filter(h => is_csp_header_name(h.name));
-
- /* When blocking remove report-only CSP headers that snitch on us. */
- headers = headers.filter(h => !is_csp_header_name(h.name, !policy.allow));
-
- if (old_signature)
- headers = headers.filter(h => h.value.search(old_signature) === -1);
-
- headers.push(...csp_headers.map(h => sanitize_csp_header(h, policy)));
-
+
const policy_str = encodeURIComponent(JSON.stringify(policy));
const signed_policy = sign_data(policy_str, new Date().getTime());
const later_30sec = new Date(new Date().getTime() + 30000).toGMTString();
@@ -67,22 +37,6 @@ function inject_csp_headers(headers, policy)
value: `hachette-${signed_policy.join("=")}; Expires=${later_30sec};`
});
- /*
- * Smuggle in the signature and the original CSP headers for future use.
- * These are signed with a time of 0, as it's not clear there is a limit on
- * how long Firefox might retain headers in the cache.
- */
- let hachette_data = {csp_headers, policy_sig: signed_policy[0]};
- hachette_data = encodeURIComponent(JSON.stringify(hachette_data));
- hachette_header.value = sign_data(hachette_data, 0).join("_");
-
- /* To ensure there is a CSP header if required */
- if (!policy.allow)
- headers.push({
- name: "content-security-policy",
- value: csp_rule(policy.nonce)
- });
-
return headers;
}
diff --git a/common/misc.js b/common/misc.js
index 91d60d2..97fc2dc 100644
--- a/common/misc.js
+++ b/common/misc.js
@@ -146,6 +146,17 @@ function sanitize_csp_header(header, policy)
return {name: header.name, value: new_csp.join('')};
}
+/* csp rule that blocks all scripts except for those injected by us */
+function make_csp_rule(policy)
+{
+ let rule = "prefetch-src 'none'; ", nonce = `'nonce-${policy.nonce}'`;
+ if (!policy.allow) {
+ rule += `script-src ${nonce}; script-src-elem ${nonce}; ` +
+ "script-src-attr 'none'; ";
+ }
+ return rule;
+}
+
/* Regexes and objects to use as/in schemas for parse_json_with_schema(). */
const nonempty_string_matcher = /.+/;
@@ -161,7 +172,7 @@ const matchers = {
/*
* EXPORTS_START
* EXPORT gen_nonce
- * EXPORT csp_rule
+ * EXPORT make_csp_rule
* EXPORT is_csp_header_name
* EXPORT nice_name
* EXPORT open_in_settings
diff --git a/content/main.js b/content/main.js
index b2cc9ed..3ebf093 100644
--- a/content/main.js
+++ b/content/main.js
@@ -17,7 +17,7 @@
* IMPORT is_chrome
* IMPORT is_mozilla
* IMPORT start_activity_info_server
- * IMPORT csp_rule
+ * IMPORT make_csp_rule
* IMPORT is_csp_header_name
* IMPORT sanitize_csp_header
* IMPORTS_END
@@ -175,9 +175,6 @@ function sanitize_meta(meta, policy)
return;
block_attribute(meta, "content");
-
- if (is_csp_header_name(http_equiv, false))
- meta.content = sanitize_csp_header({value}, policy).value;
}
function sanitize_script(script)
@@ -204,7 +201,7 @@ function apply_hachette_csp_rules(doc, policy)
{
const meta = doc.createElement("meta");
meta.setAttribute("http-equiv", "Content-Security-Policy");
- meta.setAttribute("content", csp_rule(policy.nonce));
+ meta.setAttribute("content", make_csp_rule(policy));
doc.head.append(meta);
/* CSP is already in effect, we can remove the <meta> now. */
meta.remove();
@@ -240,13 +237,15 @@ async function sanitize_document(doc, policy)
for (const meta of old_html.querySelectorAll("head meta"))
sanitize_meta(meta, policy);
- for (const script of old_html.querySelectorAll("script"))
- sanitize_script(script, policy);
+ if (!policy.allow)
+ for (const script of old_html.querySelectorAll("script"))
+ sanitize_script(script, policy);
new_html.replaceWith(old_html);
- for (const script of old_html.querySelectorAll("script"))
- desanitize_script(script, policy);
+ if (!policy.allow)
+ for (const script of old_html.querySelectorAll("script"))
+ desanitize_script(script, policy);
}
if (!is_privileged_url(document.URL)) {
@@ -282,7 +281,7 @@ if (!is_privileged_url(document.URL)) {
}
const doc_ready = Promise.all([
- policy.allow ? Promise.resolve : sanitize_document(document, policy),
+ (policy.allow && !policy.has_payload) ? Promise.resolve : sanitize_document(document, policy),
new Promise(cb => document.addEventListener("DOMContentLoaded",
cb, {once: true}))
]);