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.)