Skip to content
This repository has been archived by the owner on Jul 21, 2021. It is now read-only.

Commit

Permalink
reliably report web worker and inline script presence
Browse files Browse the repository at this point in the history
  • Loading branch information
gorhill committed Jan 1, 2018
1 parent b870757 commit 821e457
Show file tree
Hide file tree
Showing 5 changed files with 143 additions and 65 deletions.
4 changes: 3 additions & 1 deletion src/js/background.js
Original file line number Diff line number Diff line change
Expand Up @@ -110,7 +110,9 @@ return {
},

clearBrowserCacheCycle: 0,
cspNoWorkerSrc: undefined,
cspNoInlineScript: undefined,
cspNoWorker: undefined,
cspReportURI: 'about:blank',
updateAssetsEvery: 11 * oneDay + 1 * oneHour + 1 * oneMinute + 1 * oneSecond,
firstUpdateAfter: 11 * oneMinute,
nextUpdateAfter: 11 * oneHour,
Expand Down
62 changes: 57 additions & 5 deletions src/js/contentscript-start.js
Original file line number Diff line number Diff line change
Expand Up @@ -30,16 +30,68 @@

if ( typeof vAPI !== 'object' ) { return; }

window.addEventListener('securitypolicyviolation', function(ev) {
vAPI.reportedViolations = vAPI.reportedViolations || new Set();

var cspReportURI = 'about:blank';
var reportedViolations = vAPI.reportedViolations;

var handler = function(ev) {
if (
ev.isTrusted !== true ||
ev.originalPolicy.includes(cspReportURI) === false
) {
return false;
}

// Firefox and Chromium differs in how they fill the
// 'effectiveDirective' property. Need to normalize here.
var directive = ev.effectiveDirective;
if ( directive.startsWith('script-src') ) {
directive = 'script-src';
} else if ( directive.startsWith('worker-src') ) {
directive = 'worker-src';
} else if ( directive.startsWith('child-src') ) {
directive = 'worker-src';
} else {
return false;
}

var blockedURL;
try {
blockedURL = new URL(ev.blockedURI);
} catch(ex) {
}
blockedURL = blockedURL !== undefined ? blockedURL.href || '' : '';

// Avoid reporting same violations repeatedly.
var violationKey = (directive + ' ' + blockedURL).trim();
if ( reportedViolations.has(violationKey) ) {
return true;
}
reportedViolations.add(violationKey);

vAPI.messaging.send(
'contentscript.js',
{
what: 'securityPolicyViolation',
policy: ev.originalPolicy,
blockedURI: ev.blockedURI,
documentURI: ev.documentURI
directive: directive,
blockedURI: blockedURL,
documentURI: ev.documentURI,
blocked: ev.disposition === 'enforce'
}
);
});

return true;
};

document.addEventListener(
'securitypolicyviolation',
function(ev) {
if ( !handler(ev) ) { return; }
ev.stopPropagation();
ev.preventDefault();
},
true
);

})();
23 changes: 15 additions & 8 deletions src/js/contentscript.js
Original file line number Diff line number Diff line change
Expand Up @@ -407,14 +407,21 @@ var collapser = (function() {
// Mind "on..." attributes.

(function() {
vAPI.messaging.send('contentscript.js', {
what: 'contentScriptSummary',
locationURL: window.location.href,
inlineScript:
document.querySelector('script:not([src])') !== null ||
document.querySelector('a[href^="javascript:"]') !== null ||
document.querySelector('[onabort],[onblur],[oncancel],[oncanplay],[oncanplaythrough],[onchange],[onclick],[onclose],[oncontextmenu],[oncuechange],[ondblclick],[ondrag],[ondragend],[ondragenter],[ondragexit],[ondragleave],[ondragover],[ondragstart],[ondrop],[ondurationchange],[onemptied],[onended],[onerror],[onfocus],[oninput],[oninvalid],[onkeydown],[onkeypress],[onkeyup],[onload],[onloadeddata],[onloadedmetadata],[onloadstart],[onmousedown],[onmouseenter],[onmouseleave],[onmousemove],[onmouseout],[onmouseover],[onmouseup],[onwheel],[onpause],[onplay],[onplaying],[onprogress],[onratechange],[onreset],[onresize],[onscroll],[onseeked],[onseeking],[onselect],[onshow],[onstalled],[onsubmit],[onsuspend],[ontimeupdate],[ontoggle],[onvolumechange],[onwaiting],[onafterprint],[onbeforeprint],[onbeforeunload],[onhashchange],[onlanguagechange],[onmessage],[onoffline],[ononline],[onpagehide],[onpageshow],[onrejectionhandled],[onpopstate],[onstorage],[onunhandledrejection],[onunload],[oncopy],[oncut],[onpaste]') !== null
});
if (
vAPI.reportedViolations === undefined ||
vAPI.reportedViolations.has('script-src') === false
) {
if ( document.querySelector('script:not([src])') !== null ) {
vAPI.messaging.send('contentscript.js', {
what: 'securityPolicyViolation',
directive: 'script-src',
documentURI: window.location.href
});
if ( vAPI.reportedViolations ) {
vAPI.reportedViolations.add('script-src');
}
}
}

collapser.addMany(document.querySelectorAll('img'));
collapser.addIFrames(document.querySelectorAll('iframe'));
Expand Down
50 changes: 22 additions & 28 deletions src/js/messaging.js
Original file line number Diff line number Diff line change
Expand Up @@ -391,32 +391,23 @@ var µm = µMatrix;

/******************************************************************************/

var contentScriptSummaryHandler = function(tabId, details) {
// TODO: Investigate "Error in response to tabs.executeScript: TypeError:
// Cannot read property 'locationURL' of null" (2013-11-12). When can this
// happens?
if ( !details || !details.locationURL ) { return; }

// scripts
if ( details.inlineScript !== true ) {
return;
}

// https://github.com/gorhill/httpswitchboard/issues/25
var pageStore = µm.pageStoreFromTabId(tabId);
var contentScriptSummaryHandler = function(tabId, pageStore, details) {
if ( pageStore === null ) { return; }

var pageHostname = pageStore.pageHostname;
var µmuri = µm.URI.set(details.locationURL);
var µmuri = µm.URI.set(details.documentURI);
var frameURL = µmuri.normalizedURI();
var frameHostname = µmuri.hostname;

var blocked = details.blocked;
if ( blocked === undefined ) {
blocked = µm.mustBlock(pageHostname, µmuri.hostname, 'script');
}

// https://github.com/gorhill/httpswitchboard/issues/333
// Look-up here whether inline scripting is blocked for the frame.
var inlineScriptBlocked = µm.mustBlock(pageHostname, frameHostname, 'script');
var url = frameURL + '{inline_script}';
pageStore.recordRequest('script', url, inlineScriptBlocked);
µm.logger.writeOne(tabId, 'net', pageHostname, url, 'script', inlineScriptBlocked);
pageStore.recordRequest('script', url, blocked);
µm.logger.writeOne(tabId, 'net', pageHostname, url, 'script', blocked);

// https://github.com/gorhill/uMatrix/issues/225
// A good place to force an update of the page title, as at this point
Expand Down Expand Up @@ -544,16 +535,19 @@ var onMessage = function(request, sender, callback) {
break;

case 'securityPolicyViolation':
if ( request.policy !== µm.cspNoWorkerSrc ) { break; }
var url = µm.URI.hostnameFromURI(request.blockedURI) !== '' ?
request.blockedURI :
request.documentURI;
if ( pageStore !== null ) {
pageStore.hasWebWorkers = true;
pageStore.recordRequest('script', url, true);
}
if ( tabContext !== null ) {
µm.logger.writeOne(tabId, 'net', rootHostname, url, 'worker', true);
if ( request.directive === 'worker-src' ) {
var url = µm.URI.hostnameFromURI(request.blockedURI) !== '' ?
request.blockedURI :
request.documentURI;
if ( pageStore !== null ) {
pageStore.hasWebWorkers = true;
pageStore.recordRequest('script', url, true);
}
if ( tabContext !== null ) {
µm.logger.writeOne(tabId, 'net', rootHostname, url, 'worker', request.blocked);
}
} else if ( request.directive === 'script-src' ) {
contentScriptSummaryHandler(tabId, pageStore, request);
}
break;

Expand Down
69 changes: 46 additions & 23 deletions src/js/traffic.js
Original file line number Diff line number Diff line change
Expand Up @@ -299,48 +299,71 @@ var onHeadersReceived = function(details) {
var tabContext = µm.tabContextManager.lookup(tabId);
if ( tabContext === null ) { return; }

var csp = [];
var csp = [],
cspReport = [];

// If javascript is not allowed, say so through a `Content-Security-Policy`
// directive.
// We block only inline-script tags, all the external javascript will be
// blocked by our request handler.
if ( µm.cspNoInlineScript === undefined ) {
µm.cspNoInlineScript =
"script-src 'unsafe-eval' blob: *;report-uri " + µm.cspReportURI;
}
if (
µm.mustAllow(
tabContext.rootHostname,
µm.URI.hostnameFromURI(requestURL),
'script'
) !== true
) {
csp.push("script-src 'unsafe-eval' blob: *");
csp.push(µm.cspNoInlineScript);
} else {
cspReport.push(µm.cspNoInlineScript);
}

if ( µm.cspNoWorkerSrc === undefined ) {
µm.cspNoWorkerSrc = vAPI.webextFlavor.startsWith('Mozilla-') ?
"child-src 'none'; frame-src data: blob: *" :
"worker-src 'none'" ;
// TODO: Firefox will eventually support `worker-src`:
// https://bugzilla.mozilla.org/show_bug.cgi?id=1231788
if ( µm.cspNoWorker === undefined ) {
µm.cspNoWorker = vAPI.webextFlavor.startsWith('Mozilla-') ?
"child-src 'none'; frame-src data: blob: *;report-uri " :
"worker-src 'none';report-uri " ;
µm.cspNoWorker += µm.cspReportURI;
}

if ( µm.tMatrix.evaluateSwitchZ('no-workers', tabContext.rootHostname) ) {
csp.push(µm.cspNoWorkerSrc);
csp.push(µm.cspNoWorker);
} else {
cspReport.push(µm.cspNoWorker);
}

if ( csp.length === 0 ) { return; }

// If javascript is not allowed, say so through a `Content-Security-Policy`
// directive.
// We block only inline-script tags, all the external javascript will be
// blocked by our request handler.
var headers = details.responseHeaders,
cspDirectives, i;

var cspDirectives = csp.join(','),
headers = details.responseHeaders,
if ( csp.length !== 0 ) {
cspDirectives = csp.join(',');
i = headerIndexFromName('content-security-policy', headers);
// A CSP header is already present: just add our own directive as a
// separate disposition (i.e. use comma).
if ( i !== -1 ) {
headers[i].value += ',' + cspDirectives;
} else {
headers.push({ name: 'Content-Security-Policy', value: cspDirectives });
if ( i !== -1 ) {
headers[i].value += ',' + cspDirectives;
} else {
headers.push({ name: 'Content-Security-Policy', value: cspDirectives });
}
if ( requestType === 'doc' ) {
µm.logger.writeOne(tabId, 'net', '', cspDirectives, 'CSP', false);
}
}

if ( requestType === 'doc' ) {
µm.logger.writeOne(tabId, 'net', '', csp, 'CSP', false);
if ( cspReport.length !== 0 ) {
cspDirectives = cspReport.join(',');
i = headerIndexFromName('content-security-policy-report-only', headers);
if ( i !== -1 ) {
headers[i].value += ',' + cspDirectives;
} else {
headers.push({
name: 'Content-Security-Policy-Report-Only',
value: cspDirectives
});
}
}

return { responseHeaders: headers };
Expand Down

11 comments on commit 821e457

@gorhill
Copy link
Owner Author

@gorhill gorhill commented on 821e457 Jan 2, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes ok, I will reverse the change for inline scripts (can't for web workers, I need this to detect their use without using a invasive wrapper which I am not sure would be 100% reliable), and accept that uMatrix is unable to guarantee 100% detection of inline script on a page: #485 (comment).

Note that I changed my mind re. not using a CSP report, because of the two following things I became aware:

  • SecurityPolicyViolationEvent
  • Though the browser complains about it, using about:blank as report-uri does not prevent the securitypolicyviolationevent from being fired (this means no spurious network requests fired, NoScript uses https://noscript.invalid/) -- I just wish browser devs would allow non-network URI as argument to report-uri.

@gorhill
Copy link
Owner Author

@gorhill gorhill commented on 821e457 Jan 2, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Edit: this was an answer to @uBlock-user's (now removed) comment

If you are blocking 1st-party script, I can't prevent the console spamming (this was brought up in a distant past in here somewhere). But if you are not blocking 1st-party script, I can prevent it as explained above. I looked into this and finally decided that the spamming for when 1st-party scripts are not blocked will end up being annoying to many users, the alternative is acceptable. As said however, I can't do this for web workers, but they are no where near as common as inline scripts.

@uBlock-user
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sorry, I was trying to edit my comment and accidentally removed it. I can't get it back. Nvm, do what you think is best.

@gorhill
Copy link
Owner Author

@gorhill gorhill commented on 821e457 Jan 2, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It looks like uMatrix is inserting CSP policy for web-workers unconditionally.

Not unconditionally. When web workers are not forbidden, it injects only a Content-Security-Policy-Report-Only ([Report Only]), which does not prevent web workers from being used. Be sure you reload with bypassing the cache after you allow web workers, it's sometimes needed to prevent the browser from using the old headers.

@gorhill
Copy link
Owner Author

@gorhill gorhill commented on 821e457 Jan 2, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes ok, there is an issue. Investigating.

@gorhill
Copy link
Owner Author

@gorhill gorhill commented on 821e457 Jan 2, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The workers are not blocked, I can see them appearing in the debugger. There is something going on in there.

@gorhill
Copy link
Owner Author

@gorhill gorhill commented on 821e457 Jan 2, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actually, the "Continue with Google" button does not work even after I disable all extensions.

@gorhill
Copy link
Owner Author

@gorhill gorhill commented on 821e457 Jan 2, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You need to enable 3rd-party cookies, blocking 3rd-party cookies break the log in with Google.

@gorhill
Copy link
Owner Author

@gorhill gorhill commented on 821e457 Jan 2, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So I got it working with uMatrix, but I had to disable the "Block third-party cookies" setting in the browser. So not a uMatrix issue.

@gorhill
Copy link
Owner Author

@gorhill gorhill commented on 821e457 Jan 2, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Still why is it spamming

I explained above. It's necessary for uMatrix to be able to report both in the logger and the popup panel UI that a site is using web workers. When you enable the switch, uMatrix will inject a "report only" headers so that it can still report web worker usage. Without this, it would be impossible to report when web workers are in use, which is key information for a user in deciding whether web workers should be blocked or not.

Let's assume web workers are not blocked by default. Go to https://csgoconfigs.com/. That site immediately creates web workers (assuming 1st-party scripts are enabled). This will be reported in the logger and panel UI (the dot in the switch). So now users know what the site is doing.

@gorhill
Copy link
Owner Author

@gorhill gorhill commented on 821e457 Jan 2, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

When web workers are enabled, it's injecting a Content-Security-Policy-Report-Only header, not a Content-Security-Policy one.

Please sign in to comment.