In the most recent update to Apache Cordova, there was a rather important change that could really confuse you if you aren't paying attention. This is exactly the type of thing that I would have warned my readers about, but I mistakenly thought it would not impact most users. I'll explain later why I screwed that up, but I want to give huge thanks to Nic Raboy and his post, Whitelist External Resources For Use In Ionic Framework. Nic is a great blogger that I recommend following, and it is his post that led me to dig more into the changes in Cordova 5 and do my own research.
I won't repeat Nic's post here, but the summary is that how you whitelist in Cordova has changed from earlier versions. Previously whitelisting was done via an <access> tag in config.xml. The default application created by the CLI would use a * to make everything available. To repeat, by default you could use any resource in your Cordova app.
In Cordova 5, this was changed. Specifically, this was changed for Android and iOS. You can begin by looking at the whitelist guide from the Cordova docs, but this will lead you to the docs for the new whitelist plugin.
So let's talk about this plugin. If you create a new project using the default template, then this plugin is automatically added whenever you add a platform. What does this plugin do? For modern Android (KitKat and above) and all iOS versions (all supported) it uses a new security system called Content Security Policy (CSP for short). The best place to read about CSP is at MDN (https://developer.mozilla.org/en-US/docs/Web/Security/CSP). I'll do my best to explain it here though.
CSP is implemented via a meta tag in your HTML. Again, not your config.xml file but your actual HTML. This is what you'll see in the HTML file from the default template:
<meta http-equiv="Content-Security-Policy" content="default-src 'self' data: gap: https://ssl.gstatic.com 'unsafe-eval'; style-src 'self' 'unsafe-inline'; media-src *">
That's pretty weird looking, right? What you are basically seeing is a set of rules that dictate what resources can be loaded and how. You can split the above content by semicolons:
default-src 'self' data: gap: https://ssl.gstatic.com 'unsafe-eval'
style-src 'self' 'unsafe-inline'
media-src *
The beginning of each part represents a "policy directive", basically "what my security rule applies to". So for example, media-src represents audio
and video
tags. style-src represents style sheets. There's more policy directives (including script-src) to give you really fine grained control over all aspects of your application. You can find the complete list here. default-src represents a default value but it only applies to policy directives that end with -src. If that sounds confusing, wait, I'm going to make it a bit more confusing in a bit.
So that's the policy directive, what about the values after it? These values dictate what locations particular resources may be loaded from. You can use a combination of keywords, like 'self', and URLs. Let's talk keywords first. The keyword 'self' means you can use any resource served from the same location as the current document. I can't imagine a case where you wouldn't want that, but it's possible. You can use 'none' to say nothing at all is allowed. A complete list of keywords may be found here.
URLs can be of the form "scheme", ie "http:" or a scheme and domain, like http://www.cnn.com. In my testing, I was not able to use a domain by itself nor was I able to use a wildcard for the scheme. Curious about gap
? This is a special scheme for iOS and must be left there.
unsafe-eval
isn't really a location but instead represents being able to use eval() within code. I've seen some JavaScript frameworks that require this so it is probably good that it is there by default. One more that isn't in the default template is unsafe-inline
. This is a big one. Without this being in your CSP you can't use JavaScript code in your index.html file.
Now - I know all of us are good JavaScript developers and always put your code in JS files, but I know I've used inline JavaScript from time to time. Heck, on this blog I'll do it a lot just to keep the code a bit simpler. Well, this will no longer work unless you specifically modify the CSP to add unsafe-inline
. To be honest, I'd skip that and just move your code into a JavaScript file. Note that the default CSP does allow inline style sheets.
Let's consider a simple example. I created a new application and then added a CDN copy of jQuery:
<script src="http://code.jquery.com/jquery-2.1.4.min.js"></script>
To be clear - I do not recommend this. If you do this and your app is offline than your entire application is screwed.
I then used this code in my deviceReady block:
$.get("http://www.cnn.com", function(res) {
console.log(res);
$("h1").html("set to cnn");
}
Out the gate, none of this will work. You can see this yourself in your remote inspector:
First, I need to update my CSP to allow a script src at code.jquery.com:
script-src 'self' http://code.jquery.com
Notice I added 'self'! I had thought that default-src including 'self' would cover this, but it does not. As soon as I added script-src, I needed to also add 'self' here to let local scripts work.
Correcting that lets jQuery load - but guess what - there's more:
What's nice is that the error is actually pretty descriptive. In a lot of security things in the browser I've seen things silently fail so this is a big help. It is telling you that you either need to set permission in default-src, or use the policy directive connect-src. connect-src is what you want here and applies to XHR, WebSocket, and EventSource directives. Here is what I added:
connect-src http://www.cnn.com<
So... make sense? Let's get a bit more particular. First off, what happens if you screw up your CSP? Imagine the following:
<meta http-equiv="Content-Security-Policy" content="default-src 'self' data: gap: https://ssl.gstatic.com 'unsafe-eval'; style-src 'self' 'unsafe-inline'; media-src *; script-src 'self' http://code.jquery.com connect-src http://www.cnn.com">
See the error? Maybe you don't - that's the point. When running, you will get an error in the console:
I'm shocked - like seriously shocked - how darn helpful that error is. In many cases, I've seen browsers simply "swallow" security issues and say nothing. This one not only noted a syntax issue but pretty much told you exactly how to fix it. In case your curious, Google's Android debug is just as helpful:
Now let me explain why I didn't think this post was necessary. I had read about the changes, but did not think they applied by default. I was confused because I explicitly do not use the default Cordova template. Since my template did not include a CSP tag, it didn't effect me! So I began to check on this and look at the different permutations.
If you do not include the plugin and do not include the CSP, you have no access to anything.
If you do not include the plugin and do include the CSP, you have no access to anything.
If you include the plugin and a CSP, you have access to what CSP gives you access to.
If you include the plugin and do not include a CSP, your access falls back to the access tag in config.xml, which is probably * (i.e. everything allowed).
My recommendation? Use the plugin and use the CSP. It is more work and you will screw it up, trust me, but you want to do the right thing. (And later this week I'll edit my normal default Cordova template so I can practice what I preach.)
Archived Comments
Well done dude!
Very interesting, thanks a lot! When I saw about this "CSP" stuff i tried to ignore it because it looks really gibberish (all in a single line... yuck!) but now I see it will be important.
However, now I tried and it seems like mailto: and tel: schemes do not work in this CSP and must be used like in the old version, by <acces launch-external="yes" origin="mailto:"/> and <access launch-external="yes" origin="tel:"/>
If you use the cordova-plugin-whitelist plugin and not configure CSP, you'll get plenty (like a lot) of lines in the logs asking to add CSP in the html files.
One important thing if you add CSP is to test your app with a device (or simulator) that really supports it.
When I migrated my app to cordova 5, I configured CSP mostly with 'self', tested on my primary target device wich runs android jelly bean and thought I was done.
But later people tested with KitKat and nothing was working.
I'd want to add that cordova-plugin-whitelist plugin still needs to be configured in config.xml (with access origin, allow-intent or allow-navigation) in addition to CSP in html.
An alternative for people wanting to migrate quicker and not focus on new security oportunities is to add cordova-plugin-legacy-whitelist instead (then security works like with older versions)
And I found CSP was pretty well explained on this page : http://content-security-pol...
Thanks for sharing this, Nicolas!
Wrong! - well, kinda.
From the cordova whitelist plugin documentation, I was missing <allow-intent href="tel:*"/> and the same for mailto:
If you use <access origin=""> tags for tel: and mailto: it looks like you also have to add <access origin="*"> for your app to access external requests.
I am having a problem related with this topic, running on ios I have this error in the console...
Refused to execute inline script because it violates the following Content Security Policy directive: "default-src 'self' data: gap: https://ssl.gstatic.com 'unsafe-eval'". Note that 'script-src' was not explicitly set, so 'default-src' is used as a fallback.
Any clue about it?
You would need to add unsafe-inline.
my meta-tag line is : " <meta http-equiv="Content-Security-Policy" content="default-src 'self' data: gap: https://ssl.gstatic.com 'unsafe-eval'; style-src 'self' 'unsafe-inline'; media-src *"> " is not correct?
Thank you for your soon response...
unsafe-inline needs to be in default-src or in a new script-src block
now my meta-tag is: <meta http-equiv="Content-Security-Policy" content="default-src 'unsafe-inline' data: gap: https://ssl.gstatic.com 'unsafe-eval'; style-src 'self' 'unsafe-inline'; script-src 'unsafe-inline'; media-src *">
and the error: Refused to load the script 'file:///var/mobile/Containers/Bundle/Application/921B2EDB-53C4-485E-B3F6-9F66585804CF/Survey.app/www/cordova.js' because it violates the following Content Security Policy directive: "script-src 'unsafe-inline'".
:-(
Try adding 'self' to script-src too.
<meta http-equiv="Content-Security-Policy" content="
default-src 'self' data: gap: https://ssl.gstatic.com ‘unsafe-eval’;
style-src 'self' 'unsafe-inline';
script-src 'self' 'unsafe-inline';
connect-src https://enigmatic-springs-6...;
media-src *"/>
The code above does not work. I wonder why?
How does it not work? What error or unexpected behavior do you get?
<meta http-equiv="Content-Security-Policy" content="default-src 'self' *.xyz.com data: gap: https://ssl.gstatic.com *.xyz.com 'unsafe-eval'; style-src 'self' 'unsafe-inline'; media-src *;connect-src *">
error message that I am getting in visual studio when running in device is : FAILED TO LOAD RESOURE XYZ.COM but the app works fine on ripple browser.
is the issue related to csp? if so how may I fix it?
How are you trying to load stuff?
i'am using $.ajax method to post data to xyz.com/something.php page.its working well while running with ripple in chrome browser but not on my android device
In connect-src, try adding http://xyz.com
The meta tag I was using on WP8 platform (Cordova) does not work anymore for the Windows platform (appx for WP8), all onclick events are ignored. This is the tag that worked: <meta http-equiv="Content-Security-Policy" content="default-src 'self' 'unsafe-inline' 'unsafe-eval' * data:; style-src 'self' 'unsafe-inline'; media-src *; connect-src *"/>
Any ideas what could be the problem? Thanks a lot.
Hi there, the setting (connect-src) mentioned in the above nice tutorial worked fine with me in both emulator and the Android device when I read JSONP data remotely hosted on the cloud. With the same setting, I tried to run the function below to run a php file, it worked fine on the Ripple - Nexus (Galaxy) but failed on the Android device.
Do I need more setting to make the php file run on the Android device?
function SetRec(sid, grp, typ) {
var postData = $(this).serialize();
var str = "?pid=dummy + "&sid=" + sid + "&grp=" + grp + "&typ=" + typ;
$.ajax({
type: 'POST',
data: postData,
url: 'http://www.mywebdomain.com/... + str,
success: function (data) {
console.log("done.. :-)");
},
error: function () {
console.log("error ..!");
}
});
}
Solved :-)
By using cordova.InAppBrowser.open
Thanks for sharing your fix!
I am getting this error with cordova 6.2.0.Can anyone please help
TypeError: undefined is not an object (evaluating 'a.event.props.concat')
(anonymous function) — jquery.mobile-1.4.5.min.js:1104:294
(anonymous function) — jquery.mobile-1.4.5.min.js:1121
(anonymous function) — jquery.mobile-1.4.5.min.js:6
global code — jquery.mobile-1.4.5.min.js:7
Thanks.
This question is not on topic for this blog post. Please post this to StackOverflow and use the Cordova tag.
Hey, finding this years later - thanks for an awesome write up!
I spotted a formatting issue - you probably want some css like
code { white-space: pre; }
to make sure the split lines in the "separated by semi colons" code block display as intended.Enjoy the summer (:
I've changed code formatters a few times over the life time of the blog. I'll see about updating this post.