Skip to content

Commit e7b6805

Browse files
committed
Switch from HTTP to DOM event based CSP reporting in compatible browsers.
1 parent b2ac66f commit e7b6805

3 files changed

Lines changed: 49 additions & 35 deletions

File tree

src/bg/ReportingCSP.js

Lines changed: 7 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -1,31 +1,18 @@
11
"use strict";
22

3-
function ReportingCSP(reportURI, reportGroup) {
4-
const REPORT_TO_SUPPORTED = false;
5-
// TODO: figure out if we're running on a browser supporting the report-to
6-
// CSP directive, breaking report-uri, see
7-
// 1. https://www.w3.org/TR/CSP3/#directive-report-uri
8-
// 2. https://bugs.chromium.org/p/chromium/issues/detail?id=726634
9-
// 3. https://bugzilla.mozilla.org/show_bug.cgi?id=1391243
10-
11-
const REPORT_TO = {
12-
name: "Report-To",
13-
value: JSON.stringify({ "url": reportURI,
14-
"group": reportGroup,
15-
"max-age": 10886400 }),
16-
};
3+
function ReportingCSP(marker, reportURI = "") {
4+
const DOM_SUPPORTED = "SecurityPolicyViolationEvent" in window;
5+
6+
if (DOM_SUPPORTED) reportURI = "";
7+
178
return Object.assign(
18-
new CapsCSP(new NetCSP(
19-
REPORT_TO_SUPPORTED ? `;report-to ${reportGroup};`
20-
: `report-uri ${reportURI};`
9+
new CapsCSP(new NetCSP(
10+
reportURI ? `report-uri ${reportURI};` : marker
2111
)),
2212
{
2313
reportURI,
24-
reportGroup,
2514
patchHeaders(responseHeaders, capabilities) {
2615
let header = null;
27-
let needsReportTo = REPORT_TO_SUPPORTED;
28-
2916
let blocker = capabilities && this.buildFromCapabilities(capabilities);
3017
let extras = [];
3118
responseHeaders.forEach((h, index) => {
@@ -40,9 +27,6 @@ function ReportingCSP(reportURI, reportGroup) {
4027
extras.push(...this.unmergeExtras(h));
4128
}
4229
responseHeaders.splice(index, 1);
43-
} else if (needsReportTo &&
44-
h.name === REPORT_TO.name && h.value === REPORT_TO.value) {
45-
needsReportTo = false;
4630
} else if (blocker && /^(Location|Refresh)$/i.test(h.name)) {
4731
// neutralize any HTTP redirection to data: URLs, like Chromium
4832
let url = /^R/i.test(h.name)
@@ -54,9 +38,6 @@ function ReportingCSP(reportURI, reportGroup) {
5438
});
5539

5640
if (blocker) {
57-
if (needsReportTo) {
58-
responseHeaders.push(REPORT_TO);
59-
}
6041
header = this.asHeader(blocker);
6142
responseHeaders.push(header);
6243
}

src/bg/RequestGuard.js

Lines changed: 27 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,9 @@ var RequestGuard = (() => {
22
'use strict';
33
const VERSION_LABEL = `NoScript ${browser.runtime.getManifest().version}`;
44
browser.browserAction.setTitle({title: VERSION_LABEL});
5-
const REPORT_URI = "https://noscript-csp.invalid/__NoScript_Probe__/";
6-
const REPORT_GROUP = "NoScript-Endpoint";
7-
let csp = new ReportingCSP(REPORT_URI, REPORT_GROUP);
5+
const CSP_REPORT_URI = "https://noscript-csp.invalid/__NoScript_Probe__/";
6+
const CSP_MARKER = "noscript-marker";
7+
let csp = new ReportingCSP(CSP_MARKER, CSP_REPORT_URI);
88
const policyTypesMap = {
99
main_frame: "",
1010
sub_frame: "frame",
@@ -172,6 +172,19 @@ var RequestGuard = (() => {
172172
TabStatus.recordAll(sender.tab.id, message.seen);
173173
return true;
174174
},
175+
violation({url, type}, sender) {
176+
let tabId = sender.tab.id;
177+
let {frameId} = sender;
178+
let r = {
179+
url, type, tabId, frameId
180+
};
181+
Content.reportTo(r, false, policyTypesMap[type]);
182+
if (type === "script" && url === sender.url) {
183+
TabStatus.record(r, "noscriptFrame", true);
184+
} else {
185+
TabStatus.record(r, "blocked");
186+
}
187+
},
175188
async blockedObjects(message, sender) {
176189
let {url, documentUrl, policyType} = message;
177190
let TAG = `<${policyType.toUpperCase()}>`;
@@ -501,19 +514,22 @@ var RequestGuard = (() => {
501514
type,
502515
});
503516
}
517+
518+
let utf8Decoder = new TextDecoder("UTF-8");
504519
function onViolationReport(request) {
505520
try {
506-
let decoder = new TextDecoder("UTF-8");
507-
const report = JSON.parse(decoder.decode(request.requestBody.raw[0].bytes))['csp-report'];
508-
let csp = report["original-policy"]
521+
let text = utf8Decoder.decode(request.requestBody.raw[0].bytes);
522+
if (text.includes(`"inline"`)) return ABORT;
523+
let report = JSON.parse(text)["csp-report"];
524+
let originalPolicy = report["original-policy"]
509525
debug("CSP report", report);
510526
let blockedURI = report['blocked-uri'];
511527
if (blockedURI && blockedURI !== 'self') {
512528
let r = fakeRequestFromCSP(report, request);
513529
if (!/:/.test(r.url)) r.url = request.documentUrl;
514530
Content.reportTo(r, false, policyTypesMap[r.type]);
515531
TabStatus.record(r, "blocked");
516-
} else if (report["violated-directive"] === "script-src" && /; script-src 'none'/.test(report["original-policy"])) {
532+
} else if (report["violated-directive"] === "script-src" && (originalPolicy.includes("; script-src 'none'"))) {
517533
let r = fakeRequestFromCSP(report, request);
518534
Content.reportTo(r, false, "script"); // NEW
519535
TabStatus.record(r, "noscriptFrame", true);
@@ -570,8 +586,10 @@ var RequestGuard = (() => {
570586
listen("onResponseStarted", filterDocs, ["responseHeaders"]);
571587
listen("onCompleted", filterAll);
572588
listen("onErrorOccurred", filterAll);
573-
wr.onBeforeRequest.addListener(onViolationReport,
574-
{urls: [csp.reportURI], types: ["csp_report"]}, ["blocking", "requestBody"]);
589+
if (csp.reportURI) {
590+
wr.onBeforeRequest.addListener(onViolationReport,
591+
{urls: [csp.reportURI], types: ["csp_report"]}, ["blocking", "requestBody"]);
592+
}
575593
TabStatus.probe();
576594
},
577595
stop() {

src/content/content.js

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -84,6 +84,21 @@ var notifyPage = async () => {
8484

8585
window.addEventListener("pageshow", notifyPage);
8686

87+
let violations = new Set();
88+
window.addEventListener("securitypolicyviolation", e => {
89+
if (!e.isTrusted) return;
90+
let type = e.violatedDirective.split("-", 1)[0]; // e.g. script-src 'none' => script
91+
let url = e.blockedURI;
92+
if (!(url && url.includes(":"))) {
93+
url = document.URL;
94+
}
95+
let key = type + "@" + url;
96+
if (violations.has(key)) return;
97+
violations.add(key);
98+
if (type === "frame") type = "sub_frame";
99+
Messages.send("violation", {url, type});
100+
}, true);
101+
87102
ns.on("capabilities", () => {
88103
seen.record({
89104
request: {

0 commit comments

Comments
 (0)