DEV Community

Jaime Vega
Jaime Vega

Posted on • Edited on

Making the website secure by adding additional HTTP headers in Netlify (and Zeit Now)

When I started thinking about building my new website, I wanted to make sure that even though its content is not sensible or private, I wanted to make sure it follows the best-in-class security practices.

In this first iteration, I didn't want to bother also managing the back-end infrastructure, so I decided to host the website in a third-party hosting provider. There are excellent options out there, but I decided to stick with Netlify for the time being, I am not using any custom feature so it would be straightforward to migrate between services.

My goal is to get the top score in the Security Headers. If you have never used this website, it is brilliant; it gives you a score and recommendations on how to improve it.

This is the score you get with the default configuration in Netlify:

Not great, isn't it? But the site informs you about what is missing and give you links to understand more:

The cool thing about Netlify is that allows you to set your custom HTTP headers effortlessly. You need to add a file named _headers with the headers.

Make sure the file ends up in your built folder if you use Webpack or any build tool.

This is the content of the _headers file for an A+ score:

/*
  Content-Security-Policy: default-src 'none'; script-src 'self'; connect-src 'self'; img-src 'self';
  Feature-Policy: accelerometer 'none'; ambient-light-sensor 'none'; autoplay 'none'; camera 'none'; encrypted-media 'none'; fullscreen 'self'; geolocation 'none'; gyroscope 'none'; magnetometer 'none'; microphone 'none'; midi 'none'; payment 'none';  picture-in-picture 'none'; speaker 'none'; sync-xhr 'none'; usb 'none'; vr 'none';
  X-Frame-Options: DENY
  X-XSS-Protection: 1; mode=block
  Referrer-Policy: no-referrer
  X-Content-Type-Options: nosniff

Enter fullscreen mode Exit fullscreen mode

And this is how I got an A+ score:

I will explain how did I come up with these values:

  • X-Content-Type-Options: nosniff should always be set with that value.
  • X-XSS-Protection: 1; mode=block this one is to provide XSS protection in older browsers that do not support the Content Security Policy (CSP) standard.
  • X-Frame-Options: DENY this one is also added to protect browsers that do not support the CSP standard. The value should always be set to DENY unless you are planning to show your site in an iframe on another website. This is very important to protect against Clickjack attacks.

The CSP value requires more explanation. I am not going to explain the whole CSP standard; I will focus on how do I approach setting the value.

I always start with denying any resource loading by default with:

Content-Security-Policy: default-src 'none';
Enter fullscreen mode Exit fullscreen mode

And then I start adding only the necessary exceptions for the site to work. This approach is the most secure way as you don't need to fully know all the current directives available (CSP is a living standard; new directives are added regularly).

In the first iterations of this website, I only needed to be able to load scripts and images, so I add an exception for both to allow loading them only from the same domain. This means all content comes from my server and should be secure:

  Content-Security-Policy: default-src 'none'; script-src 'self'; img-src 'self';
Enter fullscreen mode Exit fullscreen mode

As the development of the website evolves, I will continue to add more directives. For example, at some point, I will have to allow loading styles, so I will have to change the policy to be like:

 Content-Security-Policy: default-src 'none'; script-src 'self'; img-src 'self'; style-src 'self';
Enter fullscreen mode Exit fullscreen mode

EDIT: There is an issue with prefetch requests. As part of CSP v3, a new directive will be introduced to define their behavior prefetch-src but it is not available in any browser, yet. But if you set default-src to none, prefetch queries are all blocked by default. Why? :(. So that gives me two options only: or don't use prefetch at all, or set the default to self... And I guess is not that bad as setting default to self is okay, but what if I have to make a prefetch to another domain, do I need to whitelist all requests to that domain?

For the Feature-Policy I followed the same approach: I disallow all the features by default, and I will start enabling them as I need them. Unfortunately, the current standard does not support denying all by default hence the length of the value.

And last but not least, I disabled the referred information Referrer-Policy as I do not need it for now. I might enable it later if I need it for Analytics purposes.

Bonus: How to add custom headers to Zeit Now.

Zeit Now also supports adding custom HTTP headers. This is the content of the now.json file to achieve the same as in Netlify:

{
  "version": 2,
  "name": "jvegadev",
  "routes": [
    {
      "src": "/.*",
      "headers": {
        "Content-Security-Policy": "default-src 'none'; script-src 'self'; connect-src 'self'; img-src 'self';",
        "X-Frame-Options": "DENY",
        "X-XSS-Protection": "1; mode=block",
        "X-Content-Type-Options": "nosniff",
        "Referrer-Policy": "no-referrer",
        "Feature-Policy": "accelerometer 'none'; ambient-light-sensor 'none'; autoplay 'none'; camera 'none'; encrypted-media 'none'; fullscreen 'self'; geolocation 'none'; gyroscope 'none'; magnetometer 'none'; microphone 'none'; midi 'none'; payment 'none';  picture-in-picture 'none'; speaker 'none'; sync-xhr 'none'; usb 'none'; vr 'none';"
      }
    }
  ]
}
Enter fullscreen mode Exit fullscreen mode

Interesting links:

Top comments (0)