0

Gmail just updated its Content Security Policy: http://googleonlinesecurity.blogspot.com/2014/12/reject-unexpected-content-security.html

This is throwing an error for my Chrome extension, which augments gmail. To be clear, my content script is loading another script that is hosted on my server. This allows for rapid deployment.

 Refused to load the script 'https://<domain-path>.js?' because it violates the following Content Security Policy directive: "script-src 'self' 'unsafe-inline' 'unsafe-eval' https://talkgadget.google.com/ https://www.googleapis.com/appsmarket/v2/installedApps/ https://www-gm-opensocial.googleusercontent.com/gadgets/js/ https://docs.google.com/static/doclist/client/js/ https://www.google.com/tools/feedback/ https://s.ytimg.com/yts/jsbin/ https://www.youtube.com/iframe_api https://ssl.google-analytics.com/ https://apis.google.com/_/scs/abc-static/ https://apis.google.com/js/ https://clients1.google.com/complete/ https://apis.google.com/_/scs/apps-static/_/js/ https://ssl.gstatic.com/inputtools/js/ https://ssl.gstatic.com/cloudsearch/static/o/js/ https://www.gstatic.com/feedback/js/ https://www.gstatic.com/common_sharing/static/client/js/ https://www.gstatic.com/og/_/js/".

This is how I load the hosted script from content script:

var script = document.createElement('script');
script.type = 'text/javascript';
script.src = 'https://<domain-path>.js';
(document.body || document.head || document.documentElement).appendChild(script);

Any thoughts?

Keven Wang
  • 1,208
  • 17
  • 28
  • Apparently you can change the csp response header using chrome extension webRequest. This is a workaround. However I'm not sure if it's a good practice. – Keven Wang Dec 17 '14 at 00:22

1 Answers1

3

You should not insert external scripts in Gmail, because it slows down the page load time and makes it harder for others to audit your extension. And you should certainly not use the webRequest API to remove the Content-Security-Policy header, because this reduces the security of Gmail.

If you really want to fetch and execute the latest version of your code in the page's context, use XMLHttpRequest to load the script, then insert a <script> tag with this code:

// NOTE: Inserting external scripts should be avoided if possible!
// Do not use this method if your extension can completely function
// without external scripts!

// Even if you have to load an external script, make sure that it is loaded over
// https:, NEVER over http: ! If you insert scripts from http:-URLs, your users'
// security can be compromised by MITM attacks.

var x = new XMLHttpRequest();
x.open('GET', 'https://example.com/script.js');
x.onload = function() {
    var s = document.createElement('script');
    s.textContent = x.responseText;
    (document.head || document.documentElement).appendChild(s);
};
x.onerror = function() {
    // Failed to load. Fallback to loading an (old version of your) script
    // that is bundled with your extension. It must be listed in the
    // "web_accessible_resources" section in your manifest file.
    var s = document.createElement('script');
    s.src = chrome.runtime.getURL('script.js');
    (document.head || document.documentElement).appendChild(s);
};
x.send();

This method does not require the 'unsafe-inline' directive, because inline scripts injected by extensions bypass the content security policy (ref).

Rob W
  • 341,306
  • 83
  • 791
  • 678
  • Thanks for the note! What if script.js loads other scripts, for versioning purposes? script.js would contain the latest file name (versioned). @Rob – Keven Wang Dec 17 '14 at 19:44
  • @Kevin Change your setup such that only one file is needed, eg using r.js optimizer (for AMD projects), or by concatenating every dependency with your main script. – Rob W Dec 17 '14 at 19:54
  • Your suggestion worked perfectly. The only issue now is that, because script is inlined, the stacktrace shows @ as origin, instead of file name. But it's a separate issue. – Keven Wang Dec 18 '14 at 18:59
  • Posted the new question here: http://stackoverflow.com/questions/27553866/inline-javascript-stacktrace-better-origin-than-anonymous – Keven Wang Dec 18 '14 at 19:04
  • I noticed a GET request this way doesn't set the cookie. For my usecase, the server actually relies on cookie to return the correct js. Is there any workaround to include the cookie? @Rob W – Keven Wang Jan 06 '15 at 23:07
  • @KevenWang Cookies *are* set in this way. Check whether the cookie is set correctly by your server. – Rob W Jan 06 '15 at 23:12
  • Setting x.withCredentials = 'true'; will make browser append the cookies! – Keven Wang Jan 06 '15 at 23:33
  • @KevenWang Cookies will be added if you add your website to your extension's "permissions" section. If you did not add the permission, `x.withCredentials = true;` will work, but only if 1) your server sends `Access-Control-Allow-Credentials: true` and `Access-Control-Allow-Origin: `. This is bad though, you don't want to leak your site's cookies to the whole site (e.g. Gmail), but only to your extension. – Rob W Jan 06 '15 at 23:37
  • "This method only works because Gmail's CSP contains the 'unsafe-inline' directive." That's incorrect. It would still work if Gmail didn't contain that. Chrome allows extensions to insert inline scripts and bypass CSP. This is important, because 'unsafe-inline' removes most of the benefits of CSP, and Gmail will most likely remove it at a later point. – Macil Jan 14 '15 at 18:21