Switch from HTTP to DOM event based CSP reporting in compatible browsers.
This commit is contained in:
parent
b2ac66f36f
commit
e7b6805bb4
|
@ -1,31 +1,18 @@
|
|||
"use strict";
|
||||
|
||||
function ReportingCSP(reportURI, reportGroup) {
|
||||
const REPORT_TO_SUPPORTED = false;
|
||||
// TODO: figure out if we're running on a browser supporting the report-to
|
||||
// CSP directive, breaking report-uri, see
|
||||
// 1. https://www.w3.org/TR/CSP3/#directive-report-uri
|
||||
// 2. https://bugs.chromium.org/p/chromium/issues/detail?id=726634
|
||||
// 3. https://bugzilla.mozilla.org/show_bug.cgi?id=1391243
|
||||
|
||||
const REPORT_TO = {
|
||||
name: "Report-To",
|
||||
value: JSON.stringify({ "url": reportURI,
|
||||
"group": reportGroup,
|
||||
"max-age": 10886400 }),
|
||||
};
|
||||
function ReportingCSP(marker, reportURI = "") {
|
||||
const DOM_SUPPORTED = "SecurityPolicyViolationEvent" in window;
|
||||
|
||||
if (DOM_SUPPORTED) reportURI = "";
|
||||
|
||||
return Object.assign(
|
||||
new CapsCSP(new NetCSP(
|
||||
REPORT_TO_SUPPORTED ? `;report-to ${reportGroup};`
|
||||
: `report-uri ${reportURI};`
|
||||
new CapsCSP(new NetCSP(
|
||||
reportURI ? `report-uri ${reportURI};` : marker
|
||||
)),
|
||||
{
|
||||
reportURI,
|
||||
reportGroup,
|
||||
patchHeaders(responseHeaders, capabilities) {
|
||||
let header = null;
|
||||
let needsReportTo = REPORT_TO_SUPPORTED;
|
||||
|
||||
let blocker = capabilities && this.buildFromCapabilities(capabilities);
|
||||
let extras = [];
|
||||
responseHeaders.forEach((h, index) => {
|
||||
|
@ -40,9 +27,6 @@ function ReportingCSP(reportURI, reportGroup) {
|
|||
extras.push(...this.unmergeExtras(h));
|
||||
}
|
||||
responseHeaders.splice(index, 1);
|
||||
} else if (needsReportTo &&
|
||||
h.name === REPORT_TO.name && h.value === REPORT_TO.value) {
|
||||
needsReportTo = false;
|
||||
} else if (blocker && /^(Location|Refresh)$/i.test(h.name)) {
|
||||
// neutralize any HTTP redirection to data: URLs, like Chromium
|
||||
let url = /^R/i.test(h.name)
|
||||
|
@ -54,9 +38,6 @@ function ReportingCSP(reportURI, reportGroup) {
|
|||
});
|
||||
|
||||
if (blocker) {
|
||||
if (needsReportTo) {
|
||||
responseHeaders.push(REPORT_TO);
|
||||
}
|
||||
header = this.asHeader(blocker);
|
||||
responseHeaders.push(header);
|
||||
}
|
||||
|
|
|
@ -2,9 +2,9 @@ var RequestGuard = (() => {
|
|||
'use strict';
|
||||
const VERSION_LABEL = `NoScript ${browser.runtime.getManifest().version}`;
|
||||
browser.browserAction.setTitle({title: VERSION_LABEL});
|
||||
const REPORT_URI = "https://noscript-csp.invalid/__NoScript_Probe__/";
|
||||
const REPORT_GROUP = "NoScript-Endpoint";
|
||||
let csp = new ReportingCSP(REPORT_URI, REPORT_GROUP);
|
||||
const CSP_REPORT_URI = "https://noscript-csp.invalid/__NoScript_Probe__/";
|
||||
const CSP_MARKER = "noscript-marker";
|
||||
let csp = new ReportingCSP(CSP_MARKER, CSP_REPORT_URI);
|
||||
const policyTypesMap = {
|
||||
main_frame: "",
|
||||
sub_frame: "frame",
|
||||
|
@ -172,6 +172,19 @@ var RequestGuard = (() => {
|
|||
TabStatus.recordAll(sender.tab.id, message.seen);
|
||||
return true;
|
||||
},
|
||||
violation({url, type}, sender) {
|
||||
let tabId = sender.tab.id;
|
||||
let {frameId} = sender;
|
||||
let r = {
|
||||
url, type, tabId, frameId
|
||||
};
|
||||
Content.reportTo(r, false, policyTypesMap[type]);
|
||||
if (type === "script" && url === sender.url) {
|
||||
TabStatus.record(r, "noscriptFrame", true);
|
||||
} else {
|
||||
TabStatus.record(r, "blocked");
|
||||
}
|
||||
},
|
||||
async blockedObjects(message, sender) {
|
||||
let {url, documentUrl, policyType} = message;
|
||||
let TAG = `<${policyType.toUpperCase()}>`;
|
||||
|
@ -501,11 +514,14 @@ var RequestGuard = (() => {
|
|||
type,
|
||||
});
|
||||
}
|
||||
|
||||
let utf8Decoder = new TextDecoder("UTF-8");
|
||||
function onViolationReport(request) {
|
||||
try {
|
||||
let decoder = new TextDecoder("UTF-8");
|
||||
const report = JSON.parse(decoder.decode(request.requestBody.raw[0].bytes))['csp-report'];
|
||||
let csp = report["original-policy"]
|
||||
let text = utf8Decoder.decode(request.requestBody.raw[0].bytes);
|
||||
if (text.includes(`"inline"`)) return ABORT;
|
||||
let report = JSON.parse(text)["csp-report"];
|
||||
let originalPolicy = report["original-policy"]
|
||||
debug("CSP report", report);
|
||||
let blockedURI = report['blocked-uri'];
|
||||
if (blockedURI && blockedURI !== 'self') {
|
||||
|
@ -513,7 +529,7 @@ var RequestGuard = (() => {
|
|||
if (!/:/.test(r.url)) r.url = request.documentUrl;
|
||||
Content.reportTo(r, false, policyTypesMap[r.type]);
|
||||
TabStatus.record(r, "blocked");
|
||||
} else if (report["violated-directive"] === "script-src" && /; script-src 'none'/.test(report["original-policy"])) {
|
||||
} else if (report["violated-directive"] === "script-src" && (originalPolicy.includes("; script-src 'none'"))) {
|
||||
let r = fakeRequestFromCSP(report, request);
|
||||
Content.reportTo(r, false, "script"); // NEW
|
||||
TabStatus.record(r, "noscriptFrame", true);
|
||||
|
@ -570,8 +586,10 @@ var RequestGuard = (() => {
|
|||
listen("onResponseStarted", filterDocs, ["responseHeaders"]);
|
||||
listen("onCompleted", filterAll);
|
||||
listen("onErrorOccurred", filterAll);
|
||||
wr.onBeforeRequest.addListener(onViolationReport,
|
||||
{urls: [csp.reportURI], types: ["csp_report"]}, ["blocking", "requestBody"]);
|
||||
if (csp.reportURI) {
|
||||
wr.onBeforeRequest.addListener(onViolationReport,
|
||||
{urls: [csp.reportURI], types: ["csp_report"]}, ["blocking", "requestBody"]);
|
||||
}
|
||||
TabStatus.probe();
|
||||
},
|
||||
stop() {
|
||||
|
|
|
@ -84,6 +84,21 @@ var notifyPage = async () => {
|
|||
|
||||
window.addEventListener("pageshow", notifyPage);
|
||||
|
||||
let violations = new Set();
|
||||
window.addEventListener("securitypolicyviolation", e => {
|
||||
if (!e.isTrusted) return;
|
||||
let type = e.violatedDirective.split("-", 1)[0]; // e.g. script-src 'none' => script
|
||||
let url = e.blockedURI;
|
||||
if (!(url && url.includes(":"))) {
|
||||
url = document.URL;
|
||||
}
|
||||
let key = type + "@" + url;
|
||||
if (violations.has(key)) return;
|
||||
violations.add(key);
|
||||
if (type === "frame") type = "sub_frame";
|
||||
Messages.send("violation", {url, type});
|
||||
}, true);
|
||||
|
||||
ns.on("capabilities", () => {
|
||||
seen.record({
|
||||
request: {
|
||||
|
|
Loading…
Reference in New Issue