Starting with Security Headers

Last updated October, 2016
tags: http headers, security
 
 

In a matter of years web sites will be running HTTPS everywhere—configuring your headers to do so correctly is fast becoming a necessary skill. To that end, we've compiled what we hope is a helpful summary of the current state of web security headers for you to reference. We'll update this page as new headers and best practices emerge, so be sure to subscribe for updates.

Enjoy and stay safe!

Contents

The Headers
Configuration and Deployment
Monitoring and Validating

Background

What are Headers?

We're not assuming anything here, so let's just say you are not really sure what headers are. When you make a request to a web server, (or more likely, when your browser does), the text that comes back starts with some keys and values we call headers, each on a line, in one big block, followed by two new lines \n\n, then finally the content you asked for.

HTTP/1.1 200 OK
Cache-Control: private, max-age=0
Content-Encoding: gzip
Content-Type: text/html; charset=UTF-8
Expires: -1
Server: gws
X-Frame-Options: SAMEORIGIN
X-XSS-Protection: 1; mode=block


<!Doctype html>
<html>
  <head>
    ...

You can see these two parts of the response using the developer tools of your preferred browser: View Source will give you everything after the \n\n, and View Response Headers (usually found on the root Network Request in Chrome or Firefox) will give you everything above.

Headers get set in broadly two places. First is in web servers like Nginx, Apache, IIS, or whatever the kids are using these days. Second is in web applications that you write, or at least can administer, like your slick Rails app, or that killer Wordpress blog you setup for your cousin. CDNs and all sorts of other things also tamper with headers, but let's just focus on those two for now.

A key distinction between whether headers get set by your application code or by your server is typically one of how general your rules are. For example, if every single response of a certain type gets the same cache-control header set, it would be easier to set in your server configuration; not only would you be sure you didn't miss anything, but your application code could then remain focused on your business logic.

For the headers discussed below, we strongly advise setting them in the web server. It's simpler and less of a fuss. Scroll down to the bottom of this post for examples of header configuration in popular systems.

One final word, many of the headers start with X- which historically meant that they were under consideration. This practice went out of favour when it became obvious that graduating headers out of X status caused a lot of confusion and needless duplication.

How Headers Can Help You

Security headers are primarily there to prevent attacks on your users, assuming they are accessing your site using a modern and compliant web browser. The main objective is to keep your site from being used as part of a scheme to trick users into leaking sensitive information. Many web applications also have online administrative components, so the user you are protecting might just be you.

A World on Mobile

Many of these headers aren't fully supported on mobile devices yet; we hope they will be at some point in the near future, especially as CSP and HPKP begin getting wider adoption. We still recommend setting these headers now; it does no harm to your mobile traffic and in time we expect they will be honored by mobile clients.

The Headers

Frame Options

X-Frame-Options

Type of threat this mitigates

The Frame Options header (see RFC here) tells a browser whether or not the site should be rendered in a child element of a parent page, specifically a <frame>, <iframe>, or <object>. If you are not acquainted with these objects, just think of them as letting you render a site within a site.

There are types of attacks, known as clickjacking, where a user is tricked into thinking they are on Site A, when in fact they are on Site B. In this way they can be fooled into providing sensitive information. It is a pain to manually reproduce the style and interactions of a legitimate looking website for such an attack, so automated tools sidestep the problem altogether by sliding the original page into an <iframe> while siphoning off user input to send back to their evil overlords.

To prevent such an attack from happening to users on your site, you can let browsers know that your domain was never intended to be rendered in any sort of object.

In theory, an attacker would then need to coop the entire browser to ignore these headers, which is again much too much work; besides, it's easier to get users to install a malicious add-on than it is to download a compromised browser.

NB. If you are using <object> tags to import SVG files, Frame Options will have to be set to SAMEORIGIN; otherwise, consider putting the SVG directly into your page—this comes with the added benefit of CSS styling and animations, which are super neato and all the rage.

Configuration options

# this one always works, use it if you can
X-Frame-Options: DENY

# these ones aren't as awesome... read the caveats below
X-Frame-Options: SAMEORIGIN
X-Frame-Options: ALLOW-FROM https://somesite.com

DENY should be your goto: it's simple and prevents a whole host of worries..

In fact, the other two settings aren't evenly supported across browsers, so we can't really recommend them. (Interesting aside: How would you tell who the parent domain was if you were an iframe inside an iframe? Browsers implemented that decision differently and so we end up with inconsistent behaviour, which is bad.)

Since everyone was so confused by the way to roll out Frame Options, a much better header was created, Content Security Policy.

Content Security Policy (CSP)

Content-Security-Policy X-Content-Security-Policy

At the time of writing, the Content Security Policy header was supported across 90% of user devices (at least CSP Level 1). However, CSP can get a bit complicated and tends to be a little tricky to configure correctly on existing applications, for reasons we'll see shortly.

Also note that only CSP Level 1 has such adoption. CSP Level 2 (which is the expanded header set), has only 70%, and critically lacks the IE/Edge families.

Also, also note that this header may be set as a META tag in the head section of HTML itself, which was rolled out as a bridge to get people to start using it. The problem with the tag of course is that embedding the security restrictions of a document in the document itself is asking for trouble. So if you're debating between the two, go for the header—we wouldn't be surprised if browsers opted to limit or cut support for the meta-tag in the future.

Type of threat this mitigates

A CSP header (or headers) aims to limit the types of resources that can be allowed on a page, to prevent the accidental or malicious inclusion and execution of a vulnerable script, font, style sheet, or whatever new and wonderful way security graduates come up with to steal our secrets.

A CSP also lets us include much of the behaviour of the Frame Options header, including a working and supported version of the ALLOW-FROM directive (see the frame-ancestors configuration option below). We encourage you to use both in conjunction.

*NB. One of the bits that can make using a CSP difficult is the default behaviour with inline scripts. A CSP is very useful in saving developers from themselves via XSS attacks, where a user somehow tricks us into inserting an unintentional <script> tag via something like a comment box or a name field.

By default, a CSP will prevent inline scripts and styles, however modern JS frameworks can make it hard to make any claims about how you should use a CSP, as they may compile down to a great deal of inline scripts and styles. This is especially true of React prior to v15, though it seems likely enough to arise again with other frameworks in the future.

A knee-jerk reaction might be to allow unsafe inline scripts and styles, which defeats much of the intent of this protection. Best to work with your existing framework in a way that plays nice, or to make extra sure you are sanitizing user input.*

Configuration options

Brace yourself, there are a few, and they are ; separated and chainable, much like a cache-control header.

CSP Level 1 gives us the following, and is widely supported.

# Lo and behold, the thousand things that have sources... thankfully a
# default was included.
Content-Security-Policy: default-src <setting>; ...
Content-Security-Policy: ... script-src <setting>; ...
Content-Security-Policy: ... style-src <setting>; ...
Content-Security-Policy: ... img-src <setting>; ...
Content-Security-Policy: ... connect-src <setting>; ...
Content-Security-Policy: ... font-src <setting>; ...
Content-Security-Policy: ... object-src <setting>; ...
Content-Security-Policy: ... media-src <setting>; ...

# Set the allowed values for base-uri sources; handy for highjacking of
# relative links.
Content-Security-Policy: ... base-uri <setting>; ...

# If you know and understand the iframe sandbox setting, here you go, otherwise
# you can probably ignore this, as you are reading an introductory article.
Content-Security-Policy: ... sandbox <setting>; ...

# In the event that a client has detected a breach of CSP policy, you may
# provide a URI where it may post that information; probably a good way to keep
# an eye on errant front-end devs, and to track potential exploit hunters.
Content-Security-Policy: ... report-uri <setting>; ...

CSP Level 2 follows on with these extra headers. Note that the frame-ancestors option below is what lets us eventually get rid of X-Frame-Options, but CSP2 isn't yet supported by IE/Edge, leaving us with only 72% market share.

# What is the allowed source of child frames and web workers?
Content-Security-Policy: ... child-src <setting>; ...
Content-Security-Policy: ... manifest-src <setting>; ...

# Where are forms allowed to go?
Content-Security-Policy: ... form-action <setting>; ...

# Key: This is where we can replace the Frame Options header
Content-Security-Policy: ... frame-ancestors <setting>; ...

# Allowed MIME types of things like <object> et co.
# IMPORTANT: Firefox doesn't support this setting as part of their CSP Level 2
Content-Security-Policy: ... plugin-types <setting>; ...

A CSP Level 3 in proposal is in the works, which should be fun. Look forward to such goodies as worker-src, disown-opener and more!

Now that we've described the keys a bit, let's look at the possible <settings> values.

* The ever-ready wildcard, which of course comes to us from the Summerian "Dingir", meaning "god". Not really, (though almost), but it is also very powerful, so use it appropriately. Setting Content-Security-Policy: script-src * pretty much defeats the purpose.

'none' Pretty much the opposite of *: So for instance style-src 'none'; would mean that yours is a site bereft of style, which would be sad.

'self' Basically, allow whatever this is, provided it came from me, and by "me" I mean the same scheme (ie. https), host (temen.io), and port (443).

other.domain.com Allows loading a class of resource from a particular domain, so for instance script-src eviladnetwork.com; would let you put in a script element with a source pointing back to your ad network provider.

*.domain.com Like the above, but lets you wildcard subdomains. Please note that while it may seem clever to collapse a.cdn-network.com and b.cdn-network.com into a single *.cdn-network.com, this is in fact opening yourself up to an attack from anyone with an asset on c.cdn-network.com. If cdn-network.com happens to be a service which an attacker can signup up for, you're in trouble.

https://other.domain.com Lets you specify that the resource must come in over HTTPS, rather than straight HTTP.

https: By itself acts a bit like Strict-Transport-Security but for the resources in question rather than the page: All matching resources must be fetched over HTTPS.

data: Allows an asset come in over a data scheme (eg. Base64). This is handy for images, and a host of performance-hacks that exploit data-encoded assets.

mediastream: Handy as a value for media-src:

blob: Also useful for img-src: settings.

filesystem: We've never used this ourselves, for completeness, know that it's there.

'unsafe-eval' Allows JS evaluation from strings and other sources; please note that this is widely regarded as a Bad Idea.

'unsafe-inline' So this is the one we really need to talk about. These days we try not to include too much inline CSS or JS, however it's not uncommon to add onclick events, or even the occasional <script> block to get around some issue with the regular pipeline. If you are adding a CSP to a legacy project, it can be hard to audit for any and all occurrences of inline content. Also, we can expect complications as the trend in web frameworks baking components with styles increases. You'll be tempted to thus set script-src and style-src to unsafe-inline to allow your code to work, but there is another way.

With CSP Level 2, we now have two new settings to help.

nonce-XXX allows you to set a nonce for the script block, meaning you may set your CSP header to be script-src 'nonce-1234567c' and your script block to be <script nonce="1234567c">. (A nonce is a thing made for onetime use and is a common term in cryptography.)

The nonce will be inject into each page request via a server side template; of course, if you can inject a proper nonce into your client code and header, we're betting you could have just avoided the script block all together, which would have been our recommendation.

A second inline mechanism exists, letting you set a hash as a source: script-src 'sha256=abcd...' pairing with the script block that matches the hash provided. You'll need to use multiple script-src hashes to match all of your inline scripts, and calculate the hashes of each, but it doesn't require programmatic nonce generation, which is a plus.

(Modern sites tend to use a lot of scripts, so this might be a bit tedious without automation, and will certainly be difficult with third party scripts, though it happens that detecting thrid-party script changes is a feature of Temen.io Plus Tier.)

'strict-dynamic' is a helpful modifier for both the nonce and sha256 values, allowing you to implicitly trust any scripts loaded by such a script. For example: script-src 'strict-dynamic' 'nonce-XXX'; would let a script <script nonce="XXX"> load other javascript and extend the trust granted it.

None of this helps with the above-mentioned onclick problem, by the way, so we're still holding our breath that CSP Level 3 brings a new solution for that.

Example configurations

Since this header is a bit more verbose than usual, it seems like a good idea to provide an example of what it looks like all together.

Content-Security-Policy: default-src 'self'; script-src 'self' www.google-analytics.com; frame-ancestors 'none';
X-Content-Security-Policy: default-src 'self'; script-src 'self' www.google-analytics.com; frame-ancestors 'none';

Why are there two lines? Well, IE11 didn't get the memo that all the cool kids were using CSP, so if you want IE11 users to also be protected by your policy, just add in this second line. (Remember when we said how hard it could be to upgrade a header out of X- status?)

In this case, we're allowing anything from our own sites to be the source of all the resources in question, and then allowing explicit references to be made for Google Analytics. Note that we're also preventing <iframe> loading, much as with the X-Frame-Options header above.

Report Only mode

One last tip: There exists a complimentary Content-Security-Policy-Report-Only header that is a great way to test your setup while rolling out into production. This way, if you have a report URI setup, you can see if anyone gets trampled while testing a new setup in production. (We'll see this pattern again with Public Key Pinning below.)

The ideal workflow here is to setup report only mode, try setting up your headers and seeing what gets reported back. Fix, rinse, repeat. The even better news is that there are services that will host reporting endpoints for you for free today, such as Report-URI. (Temen.io has a reporting feature in limited beta for Plus Tier users as well.)

XSS Protection

X-XSS-Protection

Type of threat this mitigates

As the name implies, the XSS Protection header is designed to stop XSS attacks. It started with IE8 and something called an XSS Filter by MicroSoft, and was later supported by Webkit and their XSS Auditor. In theory this header is meant to restrict the ability of attackers by being clever and essentially replacing their code if it detects shenanigans.

Unfortunately, several attacks are known that exploit this header, and the best recommendation we have is to either enable it in the brute force, block-everything mode, or turn it off explicitly—sometimes you can be too clever.

Just don't leave it on it's default setting.

Configuration options

# The default setting in many cases, and the one that has exploits
X-XSS-Protection: 1

# The brute force, block-everything variant
X-XSS-Protection: 1; mode=block

# The "shut-it-off-and-be-careful-on-the-backend" approach
X-XSS-Protection: 0

Remember our recommendation: Don't use X-XSS-Protection: 1 if you can help it, which means you should explicitly set it to mode=block.

Strict Transport Security (HSTS)

Strict-Transport-Security

Force all traffic to use secure schemes (ie. https).

Type of threat this mitigates

This is a great and easy header, as it prevents certain types of man-in-the-middle attacks that require the traffic first be downgraded, and it compliments well the goal of HTTPS Everywhere.

Many sites already use 301 redirects to push traffic towards canonical secure versions of their pages; this really helps let the browser know your intention as well.

Note that this does not force your assets to load over HTTPS; you'll get mixed content warnings if you do; the CSP https: directive is the one you want to force page assets to use HTTPS.

NB. This header can only be set over HTTPS, and so it can protect returning visitors to an HTTPS site from being tricked into an HTTP spoof, however it cannot protect a new visitor's browser from knowning that it should only be using HTTPS.

Configuration options

# For the next year, all requests to this domain must use
Strict-Transport-Security: max-age=31536000

# But why? Maybe to remove a previous setting. Remember, 0 means off.
Strict-Transport-Security: max-age=0

# And for the keeners
Strict-Transport-Security: max-age=31536000; includeSubDomains

Our recommendation is definitely max-age=31536000; includeSubDomains, just be careful about the hierarchy of your site layout. If you have this header on https://site.com that's wonderful, but if you've only set it on https://www.site.com, and you still serve a response on https://site.com, then you've missed a spot.

Content Type Options

X-Content-Type-Options

Type of threat this mitigates

This is a great one—probably the most direct in it's use and intent.

This header tells clients that the declared content-type header must match the content delivered, and to fail otherwise, which seems quite sensible and should probably have been the default all along. This prevents certain attacks, which take advantage of clients letting their usual guard down around typically safe assets, and then helpfully executing them once it realizes the content type header was set incorrectly.

Configuration options

Really basic this time:

X-Content-Type-Options: nosniff

That's it, no semicolons, separators, or cute little cryptic data directives.

Public Key Pins (HPKP)

Public-Key-Pins

Another new and complicated header with a great intent and not exactly full adoption. At time of writing, the IE/Edge browsers still haven't rolled this out, though everyone else thought it was awesome, leading to around 60% of clients supporting it in the wild.

Type of threat this mitigates

In case you've never looked into it, it might surprise you to know that the entire system of public key encryption on which our modern, secure web is based rests on a foundation of trusting a large pool of certificate authorities (CAs) not to mess up. Most browsers ship tens of trusted root certificates, and sometimes, these certificates get compromised.

So, how do you stop these rather advanced attacks from getting you? Well, if your CA is compromised, you are still out of luck; but if you want to stay safe in case someone wants to man-in-the-middle your site with a certificate issued from someone else's compromised CA, you can use Public-Key-Pins to set the Subject Public Key Information (SPKI) fingerprint and lock down who is allowed to issue your site a public key.

Who would want to do this? Anyone likely to have users in countries with, er, less than glowing reputations for Internet privacy; especially if the identity or content of your site might not be politically favourable for some. Keep in mind that this level of attack requires something closer to a state-level actor, or a really disgruntled GoDaddy administrator, or the NSA; it's a pretty open suspicion at this point that they've likely compromised a CA or two.

Configuration options

A word of caution; this header is a bit more complicated to setup, and can be disastrous if you muck it up. Please read ahead to the Report Only setting for getting started.

# The thing you definitely need, see below
Public-Key-Pins: pin-sha256="base64==<SPKI>"...

# Some other bits, to be used afterwards
# Expiry time
Public-Key-Pins: ... max-age=<expiry>; ...

# You probably want this if you use a wildcard; pretty self-explanatory
Public-Key-Pins: ... includeSubDomains; ...

# Same story as the CSP report URI
Public-Key-Pins: ... report-uri="<URI>"; ...

The big setting here to talk about is pin-sha256. This is the SPKI fingerprint of your site. There are a few ways to grab this value, but the important thing to keep in mind here is that you should use the SPKI of the intermediate certificate for your site; this will make it easier to renew your certificate.

For example:

openssl x509 -in ca-intermediary.crt -pubkey -noout | openssl rsa -pubin -outform der | openssl dgst -sha256 -binary | openssl enc -base64

Importanly, you should be aware that you can set multiple pin-sha256 values, meaning that you may have multiple certificates considered valid for your site. This is likely a very good idea given what might happen should one of your certificates encounter problems.

max-age behaves as it does in a cache header, and likewise is expressed in seconds. Keep in mind that like CSP, HPKP only secures a visitor on subsequent visits, and the window of those visits is determined here. That is to say, setting this value below the length of a session is likely not helpful. On the other side, setting this value too high and making a mistake can effectively brick your site for affected clients. Our best advice is to set this low while starting out, and to dial it up after you have survived a few certificate renewals, and are using multiple pin-sha256 values.

includeSubDomains does what it says it does, and is useful if you are using wildcard certificates.

report-uri acts the same as the CSP Report URI value, wherein any triggering of the HPKP policy gets posted to this provided URI. If you are enabling this header, you would be well advised to set this and monitor it. That's really the only way to know if your users are being affected by an error, (aside from super informative and helpful security configuration monitoring services).

Example

All together now:

Public-Key-Pins: pin-sha256="G9uiVAZWKaASuYWhhneDttWpY3oBAkE3h2+soZS7sWs="; pin-sha256="M8HztCzM3elUxkcjR2S5P4hhyBNf6lHkmjAHKui8E4a="; max-age=5184000; includeSubDomains; report-uri="https://www.example.net/hpkp-report"

Report Only

As with the CSP header, HPKP headers have a report-only variant that will save you if you mess up the first time:

Public-Key-Pins-Report-Only: ...

Use this as you are getting set up and testing the waters; it would be the height of hubris not to.

Suborigin

Suborigin

You may already be familiar with the same-origin-policy which clients implement by default to help protect against a wide range of attacks. Suborigin allows you to extend that protection by defining sections of your site as a suborigin, granting them all the isolation of a separate origin, while still allowing parent origins access to values set therein.

NB. This is a long long way from being adopted yet. Chrome has picked it up, and Firefox likely will at some point in the near future.

Type of threat this mitigates

Suborigins let you isolate some section of your application from the rest. For instance, if you have a series of single page apps served off of different paths of your domain, you can send each back with a unique Suborigin header to isolate them from one another. Suborigin extends the kinds of protection you get from the same-origin-policy policy within your site.

Configuration options

Suborigins are configured by simply setting a value such as the path.

# Make this page a unique suborigin called "login"
Suborigin: login

In this example, the login suborigin gets a fresh cookie jar, isolated from your main application. The parent origin (ie. that served without the Suborigin header) will still have access to these values, however.

CORS Headers: Access Control Allow Origins et co.

Access-Control-Allow-Origins Access-Control-Allow-Credentials Access-Control-Allow-Credentials Access-Control-Max-Age Access-Control-Allow-Methods Access-Control-Allow-Headers

Yikes. Okay, so, if you want to know what all of these are and do, this is probably not your first place to look, and the reason is simple: These headers aren't exactly here to protect your users, but rather to facilitate safe cross-domain asset reuse to work with browsers' default same-origin policy. That's generally awesome.

If you are in the business of publishing assets that need to be shared across domains, I'd suggest a good intro to CORS headers. (Or tweet us and say "Hey! I'd really like a CORS header write up next, plz and thx".)

Otherwise, not setting them causes the default browser behaviour of failing the request, which we consider secure and are happy with.

Configuring and Deploying

Configuring your web server or application is something we strongly recommend you learn how to do yourself, and to understand comprehensively. Not only is it a valuable life skill, it also saves you from a life of copy-pasta. That being said, it's always nice to offer a little refresher, and to tie everything together.

Remember that we recommend setting most of these headers in your server configuration, so we'll start there.

These are example configurations only; remember to change them to suit your needs.

NB. Always check the debugging console while working with Security Headers; there you'll find any instance of a blocked asset or triggered policy printed to the console, which should help getting a clean setup working.

Web Servers

Nginx

As with most of these server directives, the specifics of you server configuration may vary. If you are following best practices (ahem) you will have divided your site configurations into vhost files, in which you may now place the following directives.

server {
  ...
  add_header X-Frame-Options DENY;
  add_header Content-Security-Policy "default-src 'self'; script-src 'self' www.google-analytics.com; frame-ancestors 'none';";
  add_header X-Content-Security-Policy "default-src 'self'; script-src 'self' www.google-analytics.com; frame-ancestors 'none';";
  add_header X-XSS-Protection "1; mode=block;";
  add_header Strict-Transport-Security "max-age=31536000; includeSubDomains";
  add_header X-Content-Type-Options nosniff;
  ...
}

Apache

Depending on how widely you are configuring these changes, you'll want to put the following lines in a <Directory>, <Location>, <Files>, or more likely, a <VirtualHost> directive in your site.conf file. (There are lots of ways to organize server configurations so we have to trust that you know where yours are.)

<VirtualHost>
  ...
  Header set X-Frame-Options "DENY"
  Header set Content-Security-Policy "default-src 'self'; script-src 'self' www.google-analytics.com; frame-ancestors 'none';"
  # For IE11
  Header set X-Content-Security-Policy "default-src 'self'; script-src 'self' www.google-analytics.com; frame-ancestors 'none';"
  Header set X-XSS-Protection "1; mode=block"
  Header set Strict-Transport-Security "max-age=31536000; includeSubDomains"
  Header set X-Content-Type-Options "nosniff"
  ...
</VirtualHost>

Web Applications

In the event that you need to set these headers on an application level, we've included some common web frameworks below; keep in mind that we advise the headers above to be set in your web server, so these commands should be the exception rather than the rule.

Python (Django)

In Django, most of your view handlers will return a response object, in one form or another, and response objects allow dictionary-like assignment of headers:

def helloView():
  ...
  resp = HttpResponse()
  resp['Access-Control-Allow-Origin'] = '*'
  ...

See here for more details.

Python (Flask)

Like Django, Flask's response objects allow dict-like setting of header values:

@app.route('/helloworld')
def hello():
  ...
  resp = flask.Response("why, hello!")
  resp.headers['Access-Control-Allow-Origin'] = '*'
  return resp

The API docs are a good reference for clarification and edge cases.

Go

Assuming you are using the standard library, headers are set on the response writer.

func requestHandler(w http.ResponseWriter, r *http.Request) {
    ...
    w.Header().Set("Access-Control-Allow-Origin", "*")
    ...
}

NodeJS (Express)

As with most things in express, setting headers is pretty straightforward: Just call set on the response object and pass in an object with header keys and values.

app.get('/helloworld', function(req, res){
  ...
  res.set({
    'Access-Control-Allow-Origin': '*',  
  });
  ...
});

Ruby (Rails)

Rails response objects support pretty straightforward header setting:

  ...
  response.headers['Access-Control-Allow-Origin'] = '*'
  ...

PHP (Wordpress)

Most Wordpress plugins and themes will attempt to set these security headers using <meta> tags... which won't work for a few of the ones discussed above. You are better off using the send_headers hook:

add_action('send_headers', function() {
    header('Access-Control-Allow-Origin: *');
})

Of course, we will stress again that we suggest configuring these directly in your web server where possible, for all the reasons discussed above.

Monitoring and Validating

This would be a terrible tutorial if we didn't remind you that while it's a wonderful thing to set security headers, it's even better to monitor them to make sure your headers stay up and remain correctly configured as you roll out code changes.

Automated Monitoring Services

We're obviously biased; we built Temen.io's basic service with security header monitoring in mind, as well as cipher and protocol checks, and certificate status watches. Sign up here.

As Part of Unit Tests

If you have included your web server configuration in your application repository, you can conveniently test header settings as part of your CI process. We recommend Docker for standing up test services to consume your configuration, and a friendly HTTP library in the language of your choice to test your header responses. Python's Requests library is an excellent tool for standalone header checks, though crafty use of cURL and grep can work as a minimum to get you started:

curl https://test.com -Is | grep Content-Security-Policy ...

One-off services

There are some very good services online helping to raise awareness of security headers, and many include a service to run a one-off scan. Take a look at securityheaders.io as an example. Any time you touch your security header configuration, you could do worse than a manual check here.

Resources

caniuse.com - check to see how widely different headers are supported (hint, search for Public Key Pining and Content-Security-Policy).

MDN - Web Security - specifically the HPKP page, but the rest is a treasure trove and well worth reading.

IETF HPKP RFC so many initialisms in a row, but if you are up for some pretty dry reading, the HPKP RFC would be very helpful if you are considering implementing it

content-security-policy.com - a great place to keep up on the CSP options.

Mozilla Hacks: CSP - an incredible resource dedicated to CSP.

Report URI hosts free CSP and HPKP reporting endpoints.

CSP Evaluator is Google's very helpful CSP parser and automated rating engine.

 
 

Want more?

Subscribe to get new resources like this one, and to be notified of updates.

Subscribed! An email confirmation has been sent.