How to get an A+ Security Grade For Your Website  on Mozilla Observatory (Part 1 - Netlify)
Tags: security, webdev, netlify, beginners
Published: 2021-11-07 14:43:49

After rewriting the FormBlob website with Hugo, I wanted to ensure that the website adhered to security best practices and was not vulnerable to any known issues. I began looking for a measure for website security and found Mozilla Observatory. On my very first scan, I received a C grade. While not particularly bad, I wanted to rectify any flaws found to achieve the best score I could. It was also an opportunity to learn a little more about website security.

### So what is Mozilla Observatory?

The following quote is taken directly from the FAQ at <a href="https://observatory.mozilla.org/faq" target="_blank" rel="noopener">https://observatory.mozilla.org/faq</a>.

> The Observatory tests for preventative measure against cross-site scripting attacks, man-in-the-middle attacks, cross-domain information leakage, cookie compromise, content delivery network compromise, and improperly issued certificates.
>
> However, it does not test for outdated software versions, SQL injection vulnerabilities, vulnerable content management system plugins, improper password creation policies or storage procedures, and more. These are just as important as what the Observatory tests for, and site operators should not be neglectful of them simply because they score well on the Observatory.

While it may sound like a mouthful, these tests largely measure how vulnerable your website is to some of the most common malicious attacks that prey on a website developer's negligence in setting up secure networking configurations.

### FormBlob Setup

I will split the discussion in this article into two parts - one for https://formblob.com and another for https://build.formblob.com. This is because the main site is a static site built using Hugo and deployed on Netlify, while the form builder site is a React app deployed on AWS ECS behind an elastic load balancer (ELB).

This is part one of the article discussing how you would set up Netlify to achieve an A+ grade. For part two discussing how to set up Nginx behind an ELB, [click here](https://formblob.com/resources/how-i-got-an-a-security-grade-on-mozilla-observatory-nginx).

### Getting Started

In order to improve your grade for Mozilla Observatory scans, you'll need to add HTTP response headers. When deploying on Netlify, there are two ways to set up HTTP response headers. The first is to use the netlify.toml file in your root directory. This is the same file in which you set up any other Netlify deployment configurations. Below is the syntax if you're using the netlify.toml config file to set the headers.

```toml
[[headers]]
  for = "/*"
  [headers.values]
    X-Frame-Options = "DENY"
    X-XSS-Protection = "1; mode=block"
```

Alternatively, you can use a \_headers file in your public folder, which Netlify will automatically pick up when deploying. In my case, files in Hugo's static folder will be published, so I created the \_headers file in `/static`. Below is the syntax for the \_headers file.

```sh
/*
  X-Frame-Options: DENY
  X-XSS-Protection: 1; mode=block
```

Here, I will use the \_headers file to illustrate how I set up FormBlob's HTTP response headers to get that A+ grade.

### Test Scores

Mozilla Observatory scores your website on a predefined set of tests. Let's go through each of the tests individually.

![Mozilla Observatory Test Scores](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/v3xa5z902cwkujhabuxg.png)

#### Content Security Policy

Content Security Policy (CSP) gives you fine-grained control over which resources can be loaded on your website and from where such resources are allowed. It aims to protect your website from cross-site scripting (XSS) vulnerabilities. XSS vulnerabilities stem from unsafe inline Javascript and disabling this effectively eliminates most XSS attacks. However, disabling unsafe inline also means that _all_ Javascript must be loaded from `<script src="">` tags. Javascript inside `<script>` tags but not loaded via `src` will fail to execute.

Configuring CSP is a tedious process that requires you to evaluate the sources of all the scripts and styles loaded on your website. Before we dive into each policy, here's a look at the complete CSP I deployed on FormBlob. Note that I split each directive onto a separate line here for readability. When deploying, the entire CSP must be in a single line.

```sh
/*
  # Configure CSP
  Content-Security-Policy:
    default-src 'self' cloudfront.net *.cloudfront.net *.formblob.com fblob.me *.fblob.me;
    font-src 'self' https://fonts.gstatic.com data:;
    img-src https: data: blob: www.googletagmanager.com https://ssl.gstatic.com https://www.gstatic.com;
    media-src https: data: blob:;
    script-src 'self' 'unsafe-eval' https://www.googletagmanager.com https://tagmanager.google.com https://unpkg.com cloudfront.net *.cloudfront.net js.stripe.com 'sha256-dyRKDTw6FBqVppfObkFviVwYe/aFYzGE9kpTdbXngk4=';
    style-src 'self' 'unsafe-inline' fonts.googleapis.com https://tagmanager.google.com;
    frame-src 'self' *.formblob.com fblob.me *.fblob.me js.stripe.com www.youtube.com codesandbox.io;
    frame-ancestors 'self';
    upgrade-insecure-requests;
```

I used a subset of the full list of directives that are relevant to FormBlob. I will limit my discussion here to only these directives and a few others that are common. You can read more about the [full list of directives here](https://developers.google.com/web/fundamentals/security/csp). Also, as a start, I recommend using the header `Content-Security-Policy-Report-Only` instead of `Content-Security-Policy` to get a report of all the violations without breaking your website.

- [`default-src`](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Security-Policy/default-src) is a fallback directive for the other fetch directives. Directives that are specified have no inheritance, while directives that are not specified will fall back to the value of `default-src`. Here, you want to include 'self' (the origin site with the same scheme and port) and other trusted domains. I include cloudfront.net as this is the CDN that Netlify serves resources from. The recommended setting for this is `none`, which will require you to set almost every other directive.

- [`connect-src`](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Security-Policy/connect-src) provides control over fetch requests, XHR, eventsource, beacon and websockets connections. This defines any resources that you need to connect to. For websocket connections, you will have to set the relevant scheme. For example, wss://\*.formblob.com.

- [`font-src`](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Security-Policy/font-src) specifies which URLs to load fonts from. If you are using Google Fonts, this directive should include `https://fonts.gstatic.com data:`.

- [`img-src`](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Security-Policy/img-src) specifies the URLs that images can be loaded from. If you use Google Tag Manager, this directive should include `www.googletagmanager.com https://ssl.gstatic.com https://www.gstatic.com`.

- [`media-src`](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Security-Policy/media-src) specifies the URLs from which video, audio and text track resources can be loaded from.

- [`manifest-src`](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Security-Policy/manifest-src) specifies the URLs that application manifests may be loaded from.

- [`script-src`](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Security-Policy/script-src) specifies the locations from which a script can be executed from. If you use Google Tag Manager, the recommended approach is to use the [nonce method](https://developers.google.com/tag-manager/web/csp) for GTM scripts. However, deploying on Netlify restricts me from using this as I cannot generate a session unique nonce. Thus I use the fallback method `'unsafe-eval' https://www.googletagmanager.com https://tagmanager.google.com` which may lead to potential vulnerabilities. Avoid this if you can.

  - In this directive, I also include a hash keyword `'sha256-dyRKDTw6FBqVppfObkFviVwYe/aFYzGE9kpTdbXngk4='`. You may use the hashed script as a keyword to allow any script to be loaded. This hash is automatically generated in the report in the console for any script that violates the directive. You can copy the hash together with the single quotes directly into the directive to allow the script.

- [`style-src`](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Security-Policy/style-src) controls from where styles get applied to a document. This includes `<link>` elements, `@import` rules, and requests originating from a `Link` HTTP response header field. You may notice that I include the `'unsafe-inline'` keyword. This is because I use Bootstrap for styling and Bootstrap inherently uses inline Javascript for DOM manipulation. Again, this is a potential vulnerability that you should avoid if you can. If using Google Fonts and/or Google Tag Manager, you should include these `fonts.googleapis.com https://tagmanager.google.com`.

- [`frame-src`](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Security-Policy/frame-src) restricts the URLs that can be embedded on the site.

- [`form-action`](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Security-Policy/form-action) restricts the URLs which the forms can submit to.

- [`frame-ancestors`](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Security-Policy/frame-ancestors) restricts the URLs that can embed the requested resource inside of `<frame>`, `<iframe>`, `<object>`, `<embed>`, or `<applet>` elements. This directive does not fallback to default-src directive. If this is set, X-Frame-Options is ignored by user agents.

- [`upgrade-insecure-requests`](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Security-Policy/upgrade-insecure-requests) instructs user agents to rewrite URL schemes, changing HTTP to HTTPS. This directive is for websites with large numbers of old URL's that need to be rewritten.

#### Cookies

I do not use any cookies on FormBlob as yet and hence had no need to configure this. However, if you do use any cookies, ensure that your cookies are named with the `__Secure-` prefix and set with the `Secure` flag. Cookies should also have a expiration as soon as is necessary.

#### Cross-origin Resource Sharing

Cross-origin Resource Sharing (CORS) is not needed for most websites and FormBlob is no exception. This should not be set unless specifically needed.

#### HTTP Public Key Pinning

HTTP Public Key Pinning is only required for maximum risk sites. It is not recommended for most sites and you would probably not need it.

#### HTTP Strict Transport Security

HTTP Strict Transport Security (HSTS) notifies user agents to only connect to a given site over HTTPS, even if the scheme chosen was HTTP. It works in tandem with HTTP to HTTPS redirects and should be set on response headers from the HTTPS request. The recommended setting is `Strict-Transport-Security: max-age=63072000; includeSubdomains`. If the `includeSubdomains` flag is present, all requests to subdomains will also be upgraded to HTTPS. Ensure that all subdomains can handle HTTPS traffic before including this flag.

```sh
# Only connect to this site and subdomains via HTTPS for the next two years
Strict-Transport-Security: max-age=63072000; includeSubDomains; preload
```

#### HTTPS Redirection

Sites that listen on port 80 should redirect to the same resource on HTTPS. Once the redirection has occurred, HSTS ensures that all future HTTP requests are instead sent directly to the secure site. This is automatically configured by Netlify if you use Netlify's DNS. Otherwise, you will need to configure this redirection on your own server using Apache or Nginx. Redirections should be 301 redirects.

#### Referrer Policy

There are four options for this:

- `no-referrer`: never send the Referer header
- `same-origin`: send referrer, but only on requests to the same origin
- `strict-origin`: send referrer to all origins, but only the URL sans path (e.g. https://example.com/)
- `strict-origin-when-cross-origin`: send full referrer on same origin, URL sans path on foreign origin

By default, you would want to use `strict-origin-when-cross-origin`. This protects user privacy on cross-origin requests but allows you to track users using analytics within your own site.

```sh
# Only send the shortened referrer to a foreign origin, full referrer to a local host
Referrer-Policy: strict-origin-when-cross-origin
```

#### Subresource Integrity

Subresource integrity (SRI) protects against attackers modifying the contents of Javascript libraries hosted on content delivery networks (CDNs) in order to create vulnerabilities in websites that make use of that hosted library. SRI uses a hash of the library's content to verify that a library has not been changed. If it has, the website will refuse to load the library. [Here's a good resource to generate SRI hashes](https://www.srihash.org/). Below is an example of an embedded form from FormBlob using SRI.

```html
<script
  src="https://unpkg.com/@jeremyling/form-renderer@0.4.16/dist/web.min.js"
  integrity="sha512-27spyugyD2KOU0tPev6hnJ2bCeKPh5WpMzEWna4uXXCSlSQcFRDxAKZDfBhJ21lF0hyBbTD1KoOXmXJwKU5NHQ=="
  crossorigin="anonymous"
></script>
```

#### X-Content-Type-Options

X-Content-Type-Options is a header supported by Internet Explorer, Chrome and Firefox 50+ that tells user agents not to load scripts and stylesheets unless the server indicates the correct MIME type. Without this header, these browsers can incorrectly detect files as scripts and stylesheets, leading to XSS attacks.

```sh
# Prevent browsers from incorrectly detecting non-scripts as scripts
X-Content-Type-Options: nosniff
```

#### X-Frame-Options

X-Frame-Options controls where your site may be framed within an iframe. This helps to prevent clickjacking, in which an attacker frames your site within a malicious platform that tricks users into clicking on links which the attacker has control over.

```sh
# Only allow my site to frame itself
X-Frame-Options: SAMEORIGIN
```

#### X-XSS-Protection

X-XSS-Protection is a feature of Internet Explorer and Chrome that stops pages from loading when they detect reflected XSS attacks. While a strong CSP may make this header redundant, it protects users on older browsers that do not support CSP.

```sh
# Block pages from loading when they detect reflected XSS attacks
X-XSS-Protection: 1; mode=block
```

### Complete \_headers Configuration

We have successfully gone through each of the tests and set up configurations to help us pass them. If you have followed along, you should receive at least an A grade with this setup. Here's a recap of the full configuration.

```sh
/*
  # Only connect to this site and subdomains via HTTPS for the next two years
  Strict-Transport-Security: max-age=63072000; includeSubDomains; preload

  # Configure CSP
  Content-Security-Policy: default-src 'self' cloudfront.net *.cloudfront.net *.formblob.com fblob.me *.fblob.me; font-src 'self' https://fonts.gstatic.com data:; img-src https: data: blob: www.googletagmanager.com https://ssl.gstatic.com https://www.gstatic.com; media-src https: data: blob:; script-src 'self' 'unsafe-eval' https://www.googletagmanager.com https://tagmanager.google.com https://unpkg.com cloudfront.net *.cloudfront.net js.stripe.com 'sha256-dyRKDTw6FBqVppfObkFviVwYe/aFYzGE9kpTdbXngk4='; style-src 'self' 'unsafe-inline' fonts.googleapis.com https://tagmanager.google.com; frame-src 'self' *.formblob.com fblob.me *.fblob.me js.stripe.com www.youtube.com codesandbox.io; frame-ancestors 'self'; upgrade-insecure-requests;

  # X-Frame-Options tells the browser whether you want to allow your site to be framed or not. By preventing a browser from framing your site you can defend against attacks like clickjacking.
  X-Frame-Options: SAMEORIGIN

  # Prevent browsers from incorrectly detecting non-scripts as scripts
  X-Content-Type-Options: nosniff

  # X-XSS-Protection sets the configuration for the cross-site scripting filter built into most browsers.
  X-XSS-Protection: 1; mode=block

  # Referrer Policy is a new header that allows a site to control how much information the browser includes with navigations away from a document and should be set by all sites.
  Referrer-Policy: strict-origin-when-cross-origin
```

### Concluding Remarks

With an A+ security grade, [FormBlob](https://formblob.com) ranks as the most secure form building platform in the market. FormBlob respects data privacy and aims to ensure all data is secure from malicious attacks. We make an effort to meet and exceed all established security requirements. While not officially HIPAA or NIST certified due to how new we currently are, our infrastructure and security setup meet these requirements and we are confident of getting certified in the long run. Here are the Mozilla Observatory results of other popular form builders that we benchmark against.

![Benchmarks](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/9muxqykij5yyw5w3kkrs.jpg)