 So yeah, if you want to follow along with the slides, they are already posted to the session page on Drupal.org. There's a link up there. It's a little hard to read if you care about it. So I'm Jeff. I'm from Vancouver, British Columbia, Canada. If you care to find me online on Drupal.org, I'm Gappel, Twitter, GappelCA. If you're more interested in dogs and bikes, I'm also on Instagram and Strava. I work at MyPlanet where I do all sorts of Drupal-y things. And today, I first got to start with a couple scary stories. The first one doesn't look so scary. You probably have something like this on your website. You know, you're loading from some CDN somewhere, some 30-party script. In this case, this was a JavaScript utility around accessibility. The idea is that you add this JavaScript to your website, and it helps you comply with any regulations, government requirements, ADA in the U.S., and a lot of other countries have similar ones. So if you take a look at their script at the top, they give you this warning. Don't host this file on your own website. Otherwise, we won't support you. It makes sense for a company. They want, you know, to fix bugs. They want them to get out of their customers as quickly as possible. They release new features. They want the customers to have them right away. They don't want to have someone call into their support and find out you're running a script from a year ago that's broken, and they just needed to update and wasted someone's time. The problem is, in this case, one day, this shows up at the top of their file in their CDN. And, I mean, any guesses on what type of websites have strict regulatory requirements and not a whole lot of budget to meet accessibility guidelines? So one day, this shows up on a lot of government websites in Australia, the U.K., Canada, the U.S., as well as many other ones. One report was indicating at least 4,000 websites were serving this file, and all of their users are now running in CryptoMiner. So that's pretty bad. It's not terrible. I mean, it's not harvesting personal information. It's really just spinning up someone's CPU, wasting a little bit of electricity. Once this is fixed, no one's as harmed. But of course, that's not the limit of what this can do. So another case was a certain presidential campaign on their donation website. It was including a script directly from GitHub, and it was caught before anything bad happened. But if someone was able to change that, now people are inputting their personal information to support the campaign or inputting their credit card information to make a donation. And there's nothing stopping a script that's added to the page or edited from that third-party CDN from doing anything with that information that it wants. And one of the things that's popped up several times over the last few years, malware or a particular group of people who were hacking websites called Magecart, and they've injected in some pretty prominent places. British Airways had over 300,000 customers have some other personal information stolen while they were trying to update flight information. It's also a ticket master for people entering their credit card information. And this gives additional access when someone's putting in their credit card. They're putting in their CVV value, which shouldn't be stored or transferred in any other way. And it gives anyone who steals that information a lot more leeway in doing credit card fraud than they would otherwise. As for Drupal in particular, the most common example of cross-site scripting you'll see is something like here in the title field. This is a bit of a bigger risk in Drupal 7 because it's stored in the database just as it is. Drupal Core itself runs Checkplane on that value. But maybe you have a module that doesn't realize that it needs to run Checkplane on a particular value, but thinks it's filtered somewhere else and that pops onto your website. Drupal 8 has some additional protections. Twig is a great layer to help out with that. But here, the one that worries me is not that field. I know that's relatively obvious, relatively safe, but it's that body field, that full HTML. You're using the full HTML text format. You're indicating that this is HTML that I feel is safe that I want to show up on the website. So maybe you have an editor who just knows a little bit too much HTML and they're a bit of a danger. It's not going to filter them from copying and pasting the wrong bit of code. Or if their account is compromised, someone who maybe doesn't have administration privilege to the website can edit some content on the whole website and now there's a script that you don't want to be running. Of course, you know, you got those layers, filters, everything else, but there's nothing to stop. One of these popping up on a Wednesday. And this one is Drupal Core. It's a little bit more obscure in how this one worked. But a lot of the other ones that happen in Contrib are some Contrib module has an administration form, you're able to put text into it, and it didn't escape it in the way that it needed to for how it's output in some way. So, you know, there's a layer, maybe it doesn't affect you if you have those right administrative privileges, but it's always hard to tell when these are going to pop up and how much it's going to affect you. And finally, you might have a module on your website that just gives the administrator a text field. Put in some JavaScript here and we'll put it in the head of your page. So the web is pretty trusting and it traditionally has been. And, you know, it's not the web's fault, but some people did some hurtful things and took advantage of that trust. And with that, it's important to set up healthy boundaries. So that's where content security policy comes in. You know, the challenge is if the browser has JavaScript on the page, it runs it. It doesn't know if you intended that to be there or someone else put it out on the page without your permission. So especially within JavaScript, it can't tell if that's your analytics tracker that told you, you know, to paste it on the website or it's something that came from your content management system and someone pasted it into a text field. The spec, the current version of the spec is level two. This was released over two years ago in late 2016. So it's really well supported by modern browsers if you still have to support Internet Explorer. I'm sorry. It doesn't work, but for newer browsers, they are going to have this level of protection available. And of course, it's level two. The level one version of the spec was released back in 2015. So it's been quite a while that the browsers have been able to test this out, add it in, make it pretty reliable. There is also a newer working draft of the spec, level three. Most of it is a rewrite to make it a little bit more comprehensible, to make it integrate a little bit better with some of the other specs that have come out in the past few years. As for support, Chrome has the best support. There were some new options added to the spec in the last three months, and Chrome already supports them. Firefox is pretty good support, it's lagging a little bit behind. Safari is going at its own pace. I don't think it's done anything to support level three right now, but that could come at any time. And Edge doesn't support it, but of course they're changing over to the same rendering engine as Chrome in the future. And when that happens, I expect that they'll have the same level of support as Chrome. And if you're integrating within Drupal, this is my module that I'm writing to try and make it a lot easier for anyone who's a site builder who, you know, doesn't have the requirements or expertise to implement this directly in whatever your custom code doing, this hopefully makes it a lot easier. And I'll talk about some of the reasons and features for why I think that this module is probably the best approach for many people to get started. So let's spin up a website. Now Drupal Core has this helpful Umami profile. So we'll just create a new website and pop it up. And, you know, we get some things. It looks pretty good. But let's start just by locking it down. You know, let's try and pull out something we can the most, almost the most restrictive policy that we have. Security header. We're going to add a directive default. And we're just going to specify the block. Pull up the websites. We go back in time. Everything is blocked. If you look at the network request log in the console, no style sheets, no scripts, no images, the couple of icons that show up. They're only there because those are inline SVGs. Nothing else is loaded. Helpful example, not really practical. We can take that a little step back. We trust our own website. Hopefully anything from our own domain definitely has a lot more trust to it. So instead of none, we can change that to self. To say anything on our domain, we want to allow. This keyword is a little bit helpful if you're moving a site between environments. You don't have to swap out the hostname that you're trusting between your testing, staging, environments. And then we load the page again. Looks mostly back to normal. There's a couple of small differences, though. All of that text is loading the system fonts. So if we pop open the console, we can see that there is an error message. This is in Firefox. It's saying that it blocked a resource because it violated our default source directive. I'll mention Chrome. Similar errors, but a little bit more verbose. Here it's telling us that it specifically blocked a style sheet. Again, it blocked it because of the default source policy, but as well it gives us a little bit of a hint to say that there's a more specific one. If we want to control styles specifically, there's a more targeted directive where we can control that. So we'll take its suggestion here. Update it. We'll add that style source directive. We still want to allow styles from our own website with that self keyword, the font API to load that style sheet. And we'll hit another road block. One of the challenges fonts are a different resource. They need a separate directive and here those resources are again continually blocked. So one final directive. We have the font source. Since we're not loading any fonts from our site itself, we don't have to include that self and we're including the font API uses a separate domain for those font files. And one last load. We don't have any errors in our console. Everything looks as we expect it to and we're safe to go. So that's a good approach. Umami, though, is fairly simple. They're not including any third party things. You know, it's loading some limited resources. So locking down everything is a little bit risky. So getting into the module itself, when you enable it, it takes a little bit more of a conservative approach. It's not enabling that default source directive, but it is enabling that script source and style source directives. So in Drupal 8, because every external resource that you're adding to the website should be defined in libraries.yaml file for any of your modules, themes, profiles on the website. The module can parse those and generate a policy that's going to include most of the things on your website already. So this takes a safe approach. It enables those policies and that is a good step towards, you know, protecting your site out of the box. And as we can see here, there's actually two separate headers that you can send. One is report only and then one is actually enforced. So we can see here there's those two directives that it enables style source script source. It's auto-detecting the Google Font API, but because it can't tell where the CSS file is loading those fonts from, it can't enable that particular directive as well to restrict those font resources. So there is work in the future on the module to be able to have, you know, umami could define my APIs that I use also needs to set these font sources so that those can not automatically detect it, but for now it does require that manual configuration if you want to be more restrictive. And as the report only suggests is that you have a reporting option. So not just that those errors show up in their console, but the browser will tell your website. It'll send a request to an endpoint on your website that something was blocked. So now you're not only reliant on your developers to click through every page of the website and hope they got everything, but you can actually use this reporting option to release it to your users and if the user hits anything, your website will be notified. So what that looks like, this is the basic one in the just the site log, it dumps a JSON file, it's telling what URI the user was at. So in here they were browsing the home page, it blocked a font file and the directive that caused it to be restricted. So this allows us to use both of those policies to kind of progressively update restrictions to deploy a relatively safe one, so the enforced one without the report only in the name. It is only restricting that everything on the website is loaded over a secure connection and we know we might have some third party scripts, some inline scripts on our website for now. We're going to allow those inline scripts because by default they're blocked and if anything violates those, we're going to send it to one URL and then we can have a report to one that is much more restrictive to say we don't want any third party scripts to load, we want to figure out is there any inline scripts that would be blocked. The browser won't prevent the page from loading or executing those things, so the user experience isn't impacted but we'll get notifications that that is happening. We can audit our logs and say hey, we missed that we're using this analytics tracker, we need to update our policy to allow it. Otherwise our data is going to be interrupted or user experience is they're not going to be able to use this interactive component on our website and you can very gradually add additional items for the reporting options there's different handlers, the basic one is the site log, you can use any third party service that you want as well one with direct integration is report URI and they also have a wizard where they tell you to set it up with the most restrictive policy possible they collect all of your logs from people visiting your website and they'll actually try and give you an example policy to get started with of all of the domains that were requested and you can implement that policy and then try and adjust and tighten it up later as needed so some of the directives you probably care about the most I already mentioned the default source script source so there's kind of a hierarchy here if the default source is defined it controls anything basically that's fetched during the browser request so if the browser wants to load another resource it's using these SRC directives the default source controls all of them but you can be more specific so depending on what approach you want to go you can be very restrictive with your default source and loosen it up with the other ones or without each of these ones and fall back to open for one of the other ones that you haven't configured yet image source controls images, media is audio and video and then as I mentioned before where you could siphon off information someone put something into a form and then before they submit it gets sent to an external API that's what the connect source can protect you against is that whenever JavaScript tries to make a request to some service if it's not going into a domain that's allowed by that connect source it'll be blocked so even if something is injecting injected into your page it can't really do any damage because it's inhibited within the browser itself within CSP level 3 there's a couple more specific directives that have been added so to support some older utilities as well as to narrow the risk profile for certain things is it allows you to differentiate between scripts and styles that are added directly as elements so, you know, script element style element in the top of your page versus items that are added as attributes like an on click handler or style attribute directly on an element and then there's some other directives that don't inherit from default source one of the most important ones here is frame ancestors so there is a legacy non-standard header still supported in most browsers called X frame options and Drupal core by default sets this to same origin and that indicates to the browser that if another site tries to load your site in an iframe that shouldn't be allowed so this is kind of the standard newer implementation of that it has a little bit more flexibility and power in certain ways conversely there's a lot of other options to control, you know, whether your website has to be loaded over an HD2BS connection what what sites your site is allowed to have in iframes as well as for some of the newer web technologies like web workers and web apps that use a manifest or prefetch there's other directives that allow what gets loaded and controlled through those but unfortunately there are a few roadblocks if you're trying to implement this this is one of the big ones that is my frustration right now is that Drupal core doesn't use inline JavaScript at all except for CK editor 4 the challenge with that is that the buttons in the interface require on-click handlers in order to do any of their actions the long-term fix for this hopefully there's some conversations around inDrupal 9 is adding a CK editor 5 module to allow websites to update to that where it doesn't require inline JavaScript but for now you can at least mitigate this with level 3 you can add those more specific attributes to say CK editor requires those inline attributes we're going to allow those because they're required but we're going to block any JavaScript elements that are added on it to the page because we don't need those for any of our typical behavior so this again browsers that only support CSP level 2 don't get quite the same level of protection but this is backwards compatible that as browsers are updated they'll be able to better take advantage of these restrictions that you're providing for them another challenge definitely for a lot of third-party services is that they give you a code block something like this it does three things a placeholder function that all it does is it just when you call that function it stores it in a array to be executed later then it attaches the actual script that has the real functionality to the end of the page so that it's loaded after all of the remaining content and then it makes the actual API calls you know send your API key to the service so that it knows who you are what it's doing how it should handle its information and then execute it so two problems with this is it's inline code but also the format of this is to support Internet Explorer 9 this is pretty old I don't think most services should even be doing this anymore because Internet Explorer 9 was the last one that didn't support asynchronously loading scripts onto the page so to start with it's pretty quick replacement we're going to do a back load any newer browser, any modern browser supports that async attribute in the script's tag loaded that script in directly and then have a wrapper script where we're using any of our API functions that we need because in Drupal's case to load these scripts on the page you need to add them to the library's YAML file of your theme or your function now the content security policy module can parse those out the header for you as for the file itself, you do still need the particular snippet to set up that kind of placeholder function so that it logs any of the calls that happen before the real one is loaded sometime later in the request and then you add any of the API calls that you need in that file that you have within your module if you need to pass any dynamic information into it that's where Drupal settings comes in that's where on the PHP side you can say I need to pass this information to the front end it is data escaped into JSON and sent to the API as opposed to I've seen some other modules in PHP where they dynamically write JavaScript to be injected inline into the page and that's really hard to audit to figure out what it's going on and can be fragile whereas here Drupal settings is the way I think a more robust way and definitely a more secure way to pass that information to the front end if you want a practical example this is the other module I wrote for Google Analytics so it does exactly that to be able to take information from your page, your content on your website pass it through to the front end so that when you're tracking things in analytics it has that information page specific to send along through those JavaScript requests and again you can look at the code behind this module for a good example of a somewhat complex case of making that work but always I encounter developers site builders who are given an inline script and they just want to add it to the page I'm going to push back as much as possible to say that maybe they don't give you an alternative but with a little bit of knowledge, a little bit of work you can convert that into something that doesn't need inline JavaScript if you have a challenge with that reach out to me on the Drupal Slack ping me on Twitter open an issue on Stack Overflow or the Drupal Stack Exchange can probably figure out a solution that would help a lot of people to secure their websites as well but if you really need to as a last resource there is providing a hash so in this case for an inline script you would hash the contents of whatever would go in that script block it's basic CD4 coded as well and add it to the header and in this way you're indicating that that very particular script is one that I trust is safe in CSP level 2 this only works for inline scripts CSP level 3 also allows this for external scripts so you could hash your jQuery script that you're loading from a CDN and it would be parsed unfortunately because it's not supported in CSP level 2 older browsers would block it so at least for now definitely it's restricted to inline scripts obviously this presents a challenge if you have dynamic JavaScript added into the page but again you probably don't want to add a dynamically written JavaScript onto your page if this is exciting or scary to you there's definitely a lot of other things around this topic things that tie into it a couple of the ones here feature policy and reporting API are definitely very complimentary and integrate with content security policy itself so feature policy is less focused on do I trust the domain that this JavaScript is loading from and feature policy is more what do I trust this JavaScript to do is any JavaScript on my page expected to turn on the webcam play audio or record anything from the mic so that way it complements to say like if an ad network is compromised I don't want it suddenly trying to load with users webcams I trust the ad network but if they're breached let's limit the damage that can be done and reporting API is a little bit one of the challenges with that report URI option as I showed with the font one we blocked one CSP or one CSS file that was putting one error to the console when we blocked that CSS file now it was putting 10 errors to the console for those font violations and if you're setting up a reporting policy and it's sending it to your site and you have a lot of visitors to your site you might end up d-dossing yourself so my suggestion around that test your policy on your local site first without that reporting option enabled once you're comfortable once you're not getting errors then deploy it to be safe and then the reporting API actually implements a way to batch that functionality so instead of sending 10 requests the browser will wait later in that hour later in that day and send your site a whole bunch of reports all at once in one request so it's more data to parse but it's not slamming your site with a request every time someone reloads the page wondering why something doesn't look right and just trying to fix it by a reload so yeah please give me your feedback on the session and I will be around tomorrow if you're interested in more there's a few issues and I'm happy to answer questions about the content security policy module itself I got a few minutes here to answer questions but it's lunch after this as well so I'm happy to stick around if you have anything that you don't want to stand up to the mic and ask can you talk more about the library's API integration and how that works I I'm trying to think what other details to do so yeah the libraries when you're adding anything to the page you have to define what styles and scripts you're adding so basically when you're loading the page it just goes ok well what ones are loaded on the site anywhere any page on the website and it creates one policy one set of domains to add every page onto your website that's something that does have some space for improvements as I mentioned adding a hooker or an event where additional modules can say hey I also want you to control fonts or external APIs that I'm connecting to so the ideal is with that API there that you could just turn on the CSP module and it would be comprehensive and safe enough if all of the modules that you use define those policies that it needs that you can enable it right away and it would protect you and not block anything that it doesn't need to but of course there's ways around that that the module can't quite predict so it does take a little bit more of a safer approach right now you touched a little bit about the issue with the CK editor requiring the unsafe inline whatever you call it it seems like that kind of defeats the whole purpose of using content security policy would one possible solution possibly be to have the ability to define a different policy for the administrative pages within Drupal yeah I mean that's one of the areas where you know the level 3 spec allows you to say okay well I want to allow this particular format of JavaScript narrowing down what actually can be exploited that's another opportunity I'm I'm mixed about that because it's like well the administration section of the website is the part that you most want to protect against being able to execute inline but to enable it for CK editor you know you're opening it up in the most risky area but at the same time yeah for front end users that is an opportunity where there could be additional protection provided and there is an issue in the module issue queue specifically to do that to say you know is it a good idea and how do we best implement you know potentially page specific policies obviously it takes a little bit of effort and overhead to generate that for every page as opposed to doing just one that gets based on the entire website potentially something that is worthwhile to say like we can be more specific and protect pages as best as possible and loosen it up where you know in the most narrow places that it's required and maybe a different question here how critical would you say it is to use this if your site doesn't I mean if you're not allowing your editors to have HTML access you're not allowing you know have comments that users I mean if you're not if your site's more you know fairly static how much I mean definitely there is that risk of you know like I said of if an administrative users access is compromised and someone is able to pay something into a field or you know one of the CDNs that you're provided on or your hosting provider a bunch of areas where you know maybe it's not a significant risk for you maybe you know your site isn't likely to be targeted with someone with the expertise to compromise those sorts of things but it does offer protection and then the other advantage as well is that in a lot of cases you know the company with that accessibility tool they don't really have any way of knowing that their system was compromised until people said hey something what's going wrong you know why is this site loading a crypto miner and some people had to do a little bit of hunting to say this website is doing something wrong why is it is oh it's this third party service that they're using so both the sites that implemented it and the service provider itself didn't really have any way of detecting that and with that reporting option potentially you can see where people are trying to compromise you know your resources or websites prevent it from doing any damage and you can also be prepared to try and mitigate it as soon as possible because you're given that information as opposed to having some security researcher somewhere tell you hey something's broken so definitely a benefit and you know long term as more utilities don't rely on inline javascript anymore that definitely makes it you know a lot more secure for everyone to be able to enable it as a backstop something going wrong thanks