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.
The following quote is taken directly from the FAQ at https://observatory.mozilla.org/faq.
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.
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.
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.
[[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.
/* 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.
Mozilla Observatory scores your website on a predefined set of tests. Let's go through each of the tests individually.
<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.
/* # 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. 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-srcis 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-srcprovides 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-srcspecifies which URLs to load fonts from. If you are using Google Fonts, this directive should include
img-srcspecifies 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-srcspecifies the URLs from which video, audio and text track resources can be loaded from.
manifest-srcspecifies the URLs that application manifests may be loaded from.
script-srcspecifies 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 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.comwhich 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.
- In this directive, I also include a hash keyword
style-srccontrols from where styles get applied to a document. This includes
@importrules, and requests originating from a
LinkHTTP response header field. You may notice that I include the
frame-srcrestricts the URLs that can be embedded on the site.
form-actionrestricts the URLs which the forms can submit to.
frame-ancestorsrestricts the URLs that can embed the requested resource inside of
<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-requestsinstructs 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.
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 (CORS) is not needed for most websites and FormBlob is no exception. This should not be set unless specifically needed.
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 (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.
# Only connect to this site and subdomains via HTTPS for the next two years Strict-Transport-Security: max-age=63072000; includeSubDomains; preload
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.
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.
# Only send the shortened referrer to a foreign origin, full referrer to a local host Referrer-Policy: strict-origin-when-cross-origin
<script src="https://unpkg.com/@email@example.com/dist/web.min.js" integrity="sha512-27spyugyD2KOU0tPev6hnJ2bCeKPh5WpMzEWna4uXXCSlSQcFRDxAKZDfBhJ21lF0hyBbTD1KoOXmXJwKU5NHQ==" crossorigin="anonymous" ></script>
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.
# Prevent browsers from incorrectly detecting non-scripts as scripts X-Content-Type-Options: nosniff
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.
# Only allow my site to frame itself X-Frame-Options: SAMEORIGIN
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.
# Block pages from loading when they detect reflected XSS attacks X-XSS-Protection: 1; mode=block
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.
/* # 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
With an A+ security grade, FormBlob 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.