News & Updates

Securing websites via HTTP Security Headers

David Wen
David Wen
Product & Engineering

An approach using simple Rails configurations

We recently rolled out updated HTTP security headers for Comply. Following best practices with HTTP security headers can be a quick way to add an additional layer of security to a website, so we wanted to share our work here in hopes that others might find it helpful.

Intro to HTTP security headers

When a visitor makes a web request to a server, the server will respond with the requested content, as well as with response headers. A subset of those response headers have to do with security. Specifically, they tell browsers how to behave when communicating with the server and handling its content.

Screenshot of the security headers returned from Aptible Comply's API

For example, by using HTTP Strict Transport Security (HSTS, discussed below), websites can force the browser to communicate with it solely over HTTPS.

HSTS and other security headers can ensure that a website’s content is being used in a secure manner by the browser, thus reducing exposure to security vulnerabilities.

Best practices for HTTP security headers

Strict-Transport-Security: max-age=31536000
Referrer-Policy: strict-origin
Expect-CT: max-age=31536000; report-uri=<reporting-uri>
X-Frame-Options: DENY
X-Content-Type-Options: nosniff
X-XSS-Protection: 1; report=<reporting-uri>

HTTP Strict-Transport-Security (HSTS)

Strict-Transport-Security: max-age=31536000

As mentioned earlier, this tells browsers to access a website using only HTTPS. HTTPS is HTTP run via a TLS connection, which is a secured, bidirectional tunnel for data between two parties. TLS (the successor to SSL) makes three guarantees:

  1. Integrity - no intermediary can modify the data;
  2. Confidentiality - no intermediary can read the data; and,
  3. Authenticity - the server is authenticated to the client.

Non-HTTPS websites do not have the above guarantees, and are thus vulnerable to Man in the Middle (MITM) attacks. This allows an attacker to eavesdrop or alter information without the victim realizing.

Forcing browsers to visit via HTTPS eliminates the risk of unencrypted requests being intercepted. Simply redirecting HTTP requests to HTTPS is not enough - a man-in-the-middle attacker could intercept the response, strip the redirect, and impersonate the server on subsequent requests. Comparatively, setting the Strict-Transport-Security will cause the browser to immediately make the request over HTTPS.

One caveat here is that browsers only know to visit a specific website via HTTPS if they have received the HSTS header from that website in the past. And since the HSTS header has to be sent over HTTPS, visitors are still vulnerable to MITM attacks the very first time they visit a website (if done via HTTP). A solution here is to submit the website’s domain to the HSTS preload list, which is a list of websites that browsers will know to visit via HTTPS by default, so that even initial visits will be done via HTTPS.

We have set our Strict-Transport-Security header’s max-age to one year (31,536,000 seconds). The max-age duration is reset upon each visit to the website. If the user has not visited the website for that entire duration, then the browser would be allowed to visit the HTTP version again.


Referrer-Policy: strict-origin

This configures what referrer information will be sent in the Referer header (reference 1). It is possible a website has data in various urls that it does not want sent elsewhere as a Referer (e.g. IDs, password reset URLs), so configuring this reduces the risk of leaking information.

With our setting of strict-origin, we are enforcing only sending the origin domain in the Referer header, and only if the destination is just as secure (i.e. going from HTTPS to HTTPS). Going from HTTPS to HTTP would result in the Referer being omitted.


Expect-CT: max-age=31536000; report-uri=<reporting-uri>

This configures how a website reports or enforces Certificate Transparency (CT) requirements. Certificate Transparency is a system of public logs that aims to record all certificates issued by publicly trusted certificate authorities, with the goal of helping domain owners identify mistakenly or maliciously-issued certificates.

When we enable the Expect-CT header on our website, we are telling the browser to check its certificate against public CT logs. We monitor certificates issued for our domains in the CT logs to check for erroneous or malicious certificates. Hence, if the certificate cannot be found on the public CT logs, it means it is a mistaken or malicious certificate. For Comply, we are using the third-party service Report-URI for reporting, so if this happens we will be alerted. If an issued certificate is found on the public CT logs (which we monitor), then there is no cause for alarm. Note that though Google Chrome already enforces CT, the report-uri configuration is still valuable to us.

Currently, we set the max-age to one year (31,536,000 seconds). Since the Expect-CT security header intends to guarantee the authenticity of HTTPS connections, the Expect-CT security header is only useful if requests are being made over HTTPS. Thus, implementing the Expect-CT security header with the Strict-Transport-Security security header is a sensical approach.


X-Frame-Options: DENY

This indicates whether or not a browser should allow rendering of the requested page in <frame>, <iframe> or <object> tags. This helps mitigate clickjacking attacks, by ensuring that the requested page is not embedded into other websites. In fact, by setting it as DENY (as opposed to SAMEORIGIN), we prevent it from being embedded even on other pages of the same domain.


X-Content-Type-Options: nosniff

This configures how the browser treats the Content-Type header. If unset, browsers perform “MIME sniffing”, which is the browser attempting to guess the correct MIME type by looking at the resource.

This is important because handling a resource incorrectly could become an attack vector. To take advantage of this, attackers upload malicious code in files that could be misinterpreted and run by the browser as a different content type. For example, processing what the server thinks is an innocuous text file, csv, or image as a text/html file embedding harmful javascript.

Setting nosniff here, as we have done, prevents browsers from attempting that guess, though the website should make sure all of its assets actually have a proper Content-Type returned. Note that while configuring this header is relevant only if a website accepts uploads, it is cheap to implement and can prove useful in case the website allows uploads down the road.


X-XSS-Protection: 1; report=<reporting-uri>

This configures how the browser should act if it detects a cross-site scripting (XSS) attack. XSS attacks occur when attackers inject client-side scripts into websites that are viewed by others, and potentially access sensitive information that is stored, for example, in cookies. By setting this with a value of 1; report-uri=<reporting-uri>, we are telling the browser to sanitize the page if an XSS attack is detected, while also alerting us when it happens via our third-party reporting service.


Since we are using Rails as our backend, we added the following to config/application.rb:

config.action_dispatch.default_headers = {
  'Referrer-Policy' => 'strict-origin',
  'Expect-CT' => 'max-age=31536000; report-uri=<reporting-uri>',
  'X-Frame-Options' => 'DENY',
  'X-Content-Type-Options' => 'nosniff',
  'X-XSS-Protection' => '1; report=<reporting-uri>'

Our Comply app is deployed using Deploy, so we get HSTS for free simply by setting the environment variable FORCE_SSL to ‘true’.

That’s a wrap!

We hope you found some of these tips useful (or maybe even enjoyable)! Adding another layer of security to a website via HTTP security headers is a simple and effective way to further minimize security vulnerabilities. If you are as passionate as we are about data security, let’s chat!