I recently found out about the Mozilla Observatory. It is a website scanner that rates a website based on the implemented security features. Among others, it checks for the encryption standards used for HTTPS and the headers sent by the server. My page initially scored a C+. Thanks to Let’s Encrypt which provides my TLS certificate and the great Caddy web server which serves my website and automatically provides HTTPS there were no improvements to be made in this area. But some important HTTP headers were missing. I actually didn’t know a lot about those headers and I want to document what I learned.
At the time of writing a call to
curl -I https://watzek.dev, which can be used to make an HTTP HEAD call that only fetches the headers of a document, returns the following.
+ content-security-policy: default-src 'none'; frame-ancestors 'none'; form-action 'self'; base-uri 'none'; img-src 'self'; script-src 'self'; style-src 'self'; manifest-src 'self'; block-all-mixed-content; upgrade-insecure-requests;
content-type: text/html; charset=utf-8
+ feature-policy: microphone 'none'; geolocation 'none'; autoplay 'none'; accelerometer 'none'; ambient-light-sensor 'none'; battery 'none'; camera 'none'; display-capture 'none'; document-domain 'none'; encrypted-media 'none'; fullscreen 'none'; gyroscope 'none'; magnetometer 'none'; midi 'none'; payment 'none'; publickey-credentials 'none'; sync-xhr 'none'; usb 'none'; wake-lock 'none'; xr-spatial-tracking 'none'
last-modified: Mon, 24 Feb 2020 17:52:46 GMT
+ referrer-policy: same-origin
+ strict-transport-security: max-age=63072000; includeSubDomains; preload
x-clacks-overhead: GNU Terry Pratchett
+ x-content-type-options: nosniff
+ x-frame-options: DENY
+ x-xss-protection: 1; mode=block
date: Sat, 07 Mar 2020 17:41:33 GMT
The important headers are highlighted in the code block above. They are
This is where the
Content-Security-Policy (CSP) header comes into play. Among other directives, it makes it possible to explicitly define which origins are allowed for resources. In my case I set the allowed origins for resources I use to
'self' which means that the browser should only load them if they are on the same origin. This also disallows inline scripts or stylesheets.
In addition to controlling the allowed origins of resources this header also controls to which origins forms can send data with the
frame-ancestors policy is similar to setting the
X-Frame-Options header. It controls where a website may be embedded.
My site also sets the
block-all-mixed-content policy and the
upgrade-insecure-requests policy. The first policy disallows the loading of resources that are loaded with HTTP when the site is loaded via HTTPS. The second policy instructs the browser to also use HTTPS when a resource is actually linked via HTTP.
Modern browsers provide a variety of features that can be used by a website. Examples for such features are access to microphones or cameras of the user. Normally all those features are available and could be used by any code that is running on a site. The
Feature-Policy header can be used to explicitly set the features that are used by a page. This prevents the site to use features that are not explicitly enabled. For my site, I disabled nearly every feature because it doesn’t use any of them.
This header can be used to prevent the leak of sensitive information and provide visitors of the site with more privacy. Browsers usually send a
Referer [sic] header which contains the address of the previously visited page. This information can be quite useful for some features but unfortunately, it can also be used for tracking or other malicious activities. For my site, I set the header to
same-origin. This means that for requests on the same origin the whole address is set as
Referer header value but for requests to other sites, so-called cross-origin requests, no value will be set. This prevents other sites from tracking that visitors are coming from my site.
My website should only be reached via TLS encryption. By setting the
Strict-Transport-Security header browsers know that the site should only be accessed with HTTPS. This is also known as HTTP Strict Transport Security (HSTS).
The header has the value
max-age=63072000; includeSubDomains; preload. This means that browsers should remember to only access the site via HTTPS for 63072000 seconds (2 years). All subdomains should also be loaded via HTTPS. The
preload attribute is special as it is actually not part of the specification. It allows the site to be included in the HSTS list of browsers. By being included in the list browsers know that the site should be loaded via HTTPS only and they won’t even try to use HTTP. Actually all
.dev domains are automatically included in the list.
When the stylesheet
/styles/styles.css of this site is requested a
Content-Type header is sent along with the response. This header has the value
text/css; charset=utf-8. By looking at the value of the header the browser knows how the response of the server should be interpreted. In addition to looking at the value of the header browsers sometimes perform operations to determine the data type of the response. If this is the case the browser probably won’t use the content type from the header. This could potentially lead to the transformation of non-executable types to executable ones. By setting the
X-Content-Type-Options header the browser adheres to the value of the
Content-Type header and won’t perform any of those operations.
Some HTML elements like
<object> allow a website to embed another site. This can be used to make visitors think that they are on one site when the site is actually only embedded into another website. Consider for example an online banking site that is embedded into another malicious site. To prevent browsers from embedding my site I set the
X-Frame-Options header to
This header is not implemented in all browsers and support for it will probably be removed in the future. It instructs the browser what to do if it detects a cross-site scripting (XSS) attack. The value
1; mode=block instructs the browser to not render the page when an attack is detected.
It was amazing for me to see how many headers are available to secure websites. They provide fine-grained control over the security of websites. By understanding and using them properly the web can get saver for users and also the privacy of users can be protected.