I'm implementing Content Security Policy (CSP) on a site. Below is the CSP policy that I'm testing.
Content-Security-Policy: script-src 'self' 'nonce-random' 'strict-dynamic';
The site uses a third party js script library. The script library injects dynamic content on a page. The dynamic content has inline event handlers. Below is a simple HTML page with a script that mimics the site + the third party library's behavior.
<!DOCTYPE html>
<html>
<head>
<title>CSP Test Page</title>
<script nonce="random">
document.addEventListener('DOMContentLoaded', function (event) {
var el = document.createElement('button');
el.innerHTML = 'Click Me';
el.setAttribute('onclick', "doSomething()");
document.body.appendChild(el);
});
function doSomething() {
alert('I did something.');
}
</script>
</head>
<body>
</body>
</html>
The inline event handlers on the dynamically added button triggers the following error message in the Chrome console:
Refused to execute inline event handler because it violates the
following Content Security Policy directive: "script-src 'self'
'nonce-random' 'strict-dynamic'". Either the 'unsafe-inline' keyword,
a hash ('sha256-...'), or a nonce ('nonce-...') is required to enable
inline execution.
Any suggestions on how to address this issue from a CSP standpoint? I can't change the code of the third party library that is adding the dynamically generated content with the inline event handler.
CSP blocks all inline event handlers, including code added by the third-party library, so unfortunately there is no simple way to solve this without refactoring the CSP-incompatible dependency.
In the long term, CSP3 might provide the ability to whitelist trusted scripts inside event handlers via the 'unsafe-hashed-attributes' feature, but this isn't yet shipping in any stable browser.
In the meantime, one possible workaround is manually removing the attribute with the inline event handler after invoking the external library. That is, you could do something like:
var el = document.createElement('button');
library.doStuff(el);
// Now that the library has run we will change the DOM to be compatible with CSP.
el.onclick = function() { doSomething() };
el.removeAttribute('onclick');
Note that assigning a function to the onclick property directly is okay when it comes to CSP, as opposed to setting the onclick attribute on the HTML element, which is blocked by CSP because it converts a string to code. This will work and avoid CSP violations, but it only makes sense if there is a small number of instances of inline event handlers in the library; otherwise this could get quite tedious.
As a side note, your CSP might benefit from fallbacks for older browsers which don't support 'strict-dynamic', similar to this example.
Related
I have created an angular application. which gives the following error in the browser
Refused to execute inline event handler because it violates the following Content Security Policy directive: "script-src 'self'". Either the 'unsafe-inline' keyword, a hash ('sha256-...'), or a nonce ('nonce-...') is required to enable inline execution. Note that hashes do not apply to event handlers, style attributes and javascript: navigations unless the 'unsafe-hashes' keyword is present.
The reason is that angular injects the following in index.html
<link rel="stylesheet" href="styles.css" media="print" onload="this.media='all'">
Its that onload="this.media='all'"
One solution is to set "optimization" to false, in which case angular doesn't inject this code. But that doesn't sound like the best solution. Any suggestions how to do this?
An other solution, which I think is a little bit better than the previous one, is to create a wrapper component which holds all the styling from styles.scss. In my case, for this to work, I also needed to to set the encapsultaion of that wrapper component to ViewEncapsulation.None
I had to add this on production configuration on angular.json
"optimization": {
"scripts": true,
"styles": {
"minify": true,
"inlineCritical": false
},
"fonts": true
},
Because "optimization": false makes the bundle size too big.
The root cause and fix is covered here: Refused to execute inline event handler because it violates CSP. (SANDBOX) Getting Angular to do it in a better way might be a challenge.
As the error message says, you can allow it with 'unsafe-hashes', but that is a feature of CSP level 3 and only implemented in Chromium browsers.
I am getting below error multiple times in the console of developer tool of chrome for common.js file of maps.googleapis.com-
common.js:15
Refused to apply inline style because it violates the following Content Security Policy directive: "style-src 'self' https://fonts.googleapis.com https://s3.amazonaws.com https://maxcdn.bootstrapcdn.com". Either the 'unsafe-inline' keyword, a hash ('sha256-mmA4m52ZWPKWAzDvKQbF7Qhx9VHCZ2pcEdC0f9Xn/Po='), or a nonce ('nonce-...') is required to enable inline execution.
I need to use strict CSP policy so can't use unsafe-inline or unsafe-eval to relax the policy.
To support strict CSP policy, inline styling and scripting are not allowed. And it seems inline stylings have been used in the common.js of the google map api due to which I am getting the above error.
Any suggestion?
Any suggestion?
Use 'nonce-value' token in script-src and the same one in style-src. If you call the GMaps API with nonce='value' attribute:
<script async defer src='//maps.googleapis.com/maps/api/js?key=<api_key_here>&callback=initMap' nonce='base64value'></script>
The Google maps API script redistributes this nonce='base64value' into all child external scripts and inline styles blocks. You can check it in the demo of 'nonce' with Google map, just select 'nonce' checkbox.
Edit 24-07-2021:
I can confirm that:
GMap made some changes and does not redistribute nonce from script tag into styles.
A workaround by Max Visser no longer works as of now.
Therefore, unfortunately the answer is: Use 'unsafe-inline' and wait for Google to implement 'nonce' for styles.
Currently google maps requires you to have unsafe-inline in your CSP for style-src. For script-src, it still works.~~
The test of CSPlite.com granty mentioned has been adjusted; As of writing this answer the test says
At the end of April 2021, the Google maps script stopped passing the 'nonce-value' from the parent script to the blocks of child styles <style>...</style>
The temporary solution I found is to add all the styles from Google Maps to our third-party CSS code. This way you still get the error you have in console but the visual bugs resulting from style-src blocking inline styles from Google maps will go away. Method we used for this is to just copy over all the inline styles added by google maps.
If you find this answer and also want Google Maps to support distribution of nonce values to their inline style blocks again, please leave a comment here.
Edit 22-06-2021:
GMap does not officially support nonces yet. Recently a change went in that improved Maps JS's ability to handle separate nonces for scripts and styles. Now, if a site has no nonce on a <style> or <link rel="stylesheet">, this results in no nonce being applied to Maps JS stylesheets.
As a workaround, your site can include an empty <style> with the nonce provided, and GMaps JS will pick it up.
For me the solution was here: https://csplite.com/csp/test42/#styles_nonce_workaround
Adding <style nonce='base64value'></style> did the job.
I'm trying to execute this js code from an extension that modifies your new tab page.
<script>
function myFunction() {
document.getElementById("qnote-text").innerHTML = "⭕ ";
}
</script>
But I always get the same error on chrome console:
Refused to execute inline event handler because it violates the following Content Security Policy directive: "script-src 'self'. Either the 'unsafe-inline' keyword, a hash ('sha256-...'), or a nonce ('nonce-...') is required to enable inline execution.
It has to be inline.
your error message said google-chrome extension does not allow inline script. so, you have to do external script like <script src="script.js"></script>
I have a Chrome extension, and a Chrome app. I need inline install for both of them on the same domain.
As per Googles instructions (for one inline install) I add the header link tag:
<link rel="chrome-webstore-item" href="https://chrome.google.com/webstore/detail/itemID">
Then add the onclick function in the body:
<button onclick="chrome.webstore.install()" id="install-button">Add to Chrome</button>
<script>
if (chrome.app.isInstalled) {
document.getElementById('install-button').style.display = 'none';
}
</script>
What I need to know is how to add two instances. One for the extension, and one for the app. Do I add two link tags in the header, then edit the onclick function?
This is what Google says to do for multiple instances, but I don't understand where to edit the onclick function to differentiate between the two.
To actually begin inline installation, the
chrome.webstore.install(url, successCallback, failureCallback)
function must be called. This function can only be called in response
to a user gesture, for example within a click event handler; an
exception will be thrown if it is not. The function can have the
following parameters:
url (optional string) If you have more than one tag on your
page with the chrome-webstore-item relation, you can choose which item
you'd like to install by passing in its URL here. If it is omitted,
then the first (or only) link will be used. An exception will be
thrown if the passed in URL does not exist on the page.
successCallback (optional function) This function is invoked when
inline installation successfully completes (after the dialog is shown
and the user agrees to add the item to Chrome). You may wish to use
this to hide the user interface element that prompted the user to
install the app or extension.
failureCallback (optional function) This
function is invoked when inline installation does not successfully
complete. Possible reasons for this include the user canceling the
dialog, the linked item not being found in the store, or the install
being initiated from a non-verified site. The callback is given a
failure detail string as a parameter. You may wish to inspect or log
that string for debugging purposes, but you should not rely on
specific strings being passed back.
I currently have one link tag in my header for the extension. I need to add another inline installation, on a different page, same domain, but this second onclick code needs to be different so it doesn't refer to the existing link tag in my header.
Many thanks.
<link rel="chrome-webstore-item" href="https://chrome.google.com/webstore/detail/itemID1">
<link rel="chrome-webstore-item" href="https://chrome.google.com/webstore/detail/itemID2">
<button onclick="chrome.webstore.install('https://chrome.google.com/webstore/detail/itemID1')" id="install-button-1">Add App to Chrome</button>
<button onclick="chrome.webstore.install('https://chrome.google.com/webstore/detail/itemID2')" id="install-button-2">Add Extension to Chrome</button>
The very same docs page shows a method for the extensions.
Basically, your extension can inject a <div id="somethingYouExpect"> into the DOM, and the page's script can detect that.
It's a bit clunky though: I was trying to get it to work for test code and didn't manage to do so in a good way, as content scripts are injected either before DOM is constructed at all or after document ready fires. You can bypass that with mutation observers, but meh and your button will be visible for a split second.
You can save yourself some pain, if you're just hiding an element, by injecting a css file hiding it. Or, you can hide the elements from injected code. Either way is somewhat layout-sensitive though.
If you HAVE to be layout-independent and at the same time want something more complex than element hiding, either go the (div inject + mutation observer) route or you can try window.postMessage approach to signal the page to hide the element.
Step by step guide for the extension / CSS variant.
Suppose your extension install UI is contained in an element with id extension-install.
Add a content script to the manifest file:
"content_scripts": [
{
"matches": ["*://yourdomain/*"],
"css": ["iaminstalled.css"],
"run_at": "document_start"
}
],
The CSS:
#extension-install {
display: none !important;
}
So, to recap:
To allow installs of both the app and the extension, you need two <link> tags in the head
To install either you pass the url parameter to chrome.webstore.install
If the app is installed, it will define chrome.app.isInstalled in the page's context. You can check for it from the page to hide the install button.
If the extension is installed, it can inject CSS/JS into page to hide the install button.
Is it possible to load a remote webpage into a background page using a chrome extension?
"background": {
"page": "local.html"
}
works, but
"background": {
"page": "http://...."
}
fails with the following error:
Could not load background page http://....
No, that's not possible. It is possible since Chrome 22 - see the bottom of the answer.
You can whitelist a https: resource in the manifest file file, so that your background script can manually be constructed. Make sure that you include a fallback resource in your extension, in the case that the network is down:
<!-- ... doctype etc ... (background.html) -->
<script src="https://..../external_bg.js"></script>
<script src="bg.js"></script>
Because of the Content security policy (CSP), you cannot run inline JavaScript, so you have to use external JS files. bg.js may look like:
if (!window.namespace_of_external_bg) {
// Fallback, by defining fallback methods or injecting a new script:
document.write('<script src="fallback_bg.js"></script>');
}
If you want to dynamically construct a page, avoid use of eval-like methods, because these are also forbidden by the CSP. You can write a template, and request external values to populate your template. localStorage can be used to cache variables. For an example on caching external resources, see Chrome extension adding external javascript to current page's html. This answer referred to Content scripts, so the exact method cannot be used to enable caching scripts (because you would need to use eval to load the script). However, the caching technique can still be used.
I have also tried to use the following method, which does not work (included in this answer, so that you don't have to try it yourself):
Create a Blob from the AJAX response, then use webkitURL.createObjectURL to create a temporary URL to load the resource.
// Modification of https://stackoverflow.com/a/10371025
// Instead of `chrome.tabs.executeScript`, use
// x.responseText or x.response (eg when using x.responseType='arraybuffer')
var blob = new Blob([x.responseText], {type: 'application/javascript'});
var url = (window.URL || window.webkitURL).createObjectURL(blob);
var s = document.createElement('script');
s.src = url;
document.head.appendChild(s);
The previous code yields the following error:
Refused to load the script 'blob:chrome-extension%3A//damgmplfpicjkeogacmlgiceidmilllf/96356d24-3680-4188-812e-5661d23e81df' because it violates the following Content Security Policy directive: "script-src 'self' chrome-extension-resource:".
Loading external resources in the background page
Since Chrome 22, it is technically possible (using the unsafe-eval CSP policy) to load non-https resources in the background page. This obviously not recommended because of security concerns (because it's susceptible to the MITM attack, for instance).
Here's an example to load an arbitrary resource and run it in the context of the background script.
function loadScript(url) {
var x = new XMLHttpRequest();
x.onload = function() {
eval(x.responseText); // <---- !!!
};
x.open('GET', url);
x.send();
}
// Usage:
loadScript('http://badpractic.es/insecure.js');
The unsafe-eval CSP policy must be specified.
As usual, to make cross-origin requests, the URL must be whitelisted in the manifest at the permissions section, or the server must enable CORS.
So, the manifest should at least contain:
"content_security_policy": "script-src 'self' 'unsafe-eval'; object-src 'self'",
"permissions": ["http://badpractic.es/insecure.js"],
"background": {"scripts": ["background.js"] }