Miskatonic University Press

HTTP header security tweaks

security web

A Mastodon note last night (I still can’t bring myself to say “toot”) from @teledyn@mstdn.ca pointed out the HTTP Observatory, which “enhances web security by analyzing compliance with best security practices.” You put in a URL and it gives you a score on the site’s security, with tips on how to make it better.

I put in this site and got 65/100. With a bit of work I got to 115/100! I was pretty pleased about that. Then I discovered I had broken some small things because the settings were too restrictive. I fixed that and now I get 125/100!

Here are some notes about that, in case it’s useful to me in the future or anyone else before then. Note: I am no expert, I’m not even a web developer any more. I’m just a person running a static web site built with Jekyll who hacked on some HTTP headers.

Headers

Header information is normally used by your browser to understand what it should do with the web page content that follows. It’s rarely of interest to people unless they’re doing something technical. If you run curl --head https://www.miskatonic.org you can see only the headers for this site, which look like this:

HTTP/1.1 200 OK
Date: Wed, 24 Jul 2024 21:04:27 GMT
Server: Apache
Last-Modified: Wed, 24 Jul 2024 20:18:14 GMT
ETag: "ca2e-61e03fa641ef7"
Accept-Ranges: bytes
Content-Length: 51758
X-Clacks-Overhead: GNU Terry Pratchett
Content-Security-Policy: default-src 'self'; style-src 'unsafe-inline' 'self'; font-src 'self'; media-src 'self' data: 'self'; frame-ancestors 'none'
X-Frame-Options: DENY
Referrer-Policy: no-referrer
X-Content-Type-Options: nosniff
Cross-Origin-Resource-Policy: same-origin
Strict-Transport-Security: max-age=31536000; includeSubDomains; preload
Content-Type: text/html

(No cookies are set because there are no cookies. There is also no logging. I know nothing about anyone who looks at this site.)

Everything up to Content-Length is generated by the web server, which here is Apache hosted on Pair. Everything after that is set by me. Here’s a rundown. Browse Practical security implementation guides for more about all this.

X-Clacks-Overhead

Header set X-Clacks-Overhead "GNU Terry Pratchett"

(This is how I set the header in the .htaccess file where I can configure things. You might set it differently in your situation.)

This remembers Terry Pratchett. See X-Clacks-Overhead. This has nothing to do with security; I just think Terry Pratchett is one of the greatest writers of the last fifty years.

Content-Security-Policy

Header set Content-Security-Policy "default-src 'self'; style-src 'unsafe-inline' 'self'; font-src 'self'; media-src 'self' data: 'self'; frame-src 'self' https://www.youtube-nocookie.com/; frame-ancestors 'none'"

This was the trickiest to get working properly. See Content-Security-Policy and Content Security Policy (CSP) implementation on MDN (which I think of as the Mozilla Developer Network) and this Content Security Policy Reference for more. This is to prevent cross-site scripting attacks.

Now, I don’t have any JavaScript running on my site, so it’s not going to be a problem. Well, I don’t have any of my own JavaScript, but I do sometimes embed a YouTube video, such as in this post about Molly White’s Become a Wikipedian in 30 Minutes video. To make that work I have to have that special frame-src configuration.

As I understand it, default-src being ‘self’ means that by default, while looking at this site the browser should only load resources from this site, unless otherwise specified. Having style-src as ‘unsafe-inline’ means I can set CSS inline, specifying font-size: smaller right in a web page. It should be in a CSS file, and maybe I’ll fix that one day, but I’m not going to fuss about it right now. In media-src there’s an extra data: that says it’s all right to load media (such as images) that are specified right in the HTML with a data URL. I had to add this to do away with a warning, which confused me because I never load any media this way. Maybe I’ll figure it out later.

Using Firefox’s developer tools was a huge help in figuring all this out. I’d load the site, hit Ctrl-Shift I to pop it up, go to the Console, and reload over and over as I tweaked settings. (Whether or not JavaScript was enabled changed things, so test with it off and on. Privacy settings and add-ons may also affect things.)

This isn’t as tightly locked down as it could be, but it’s much better than it was before.

X-Frame-Options

Header set X-Frame-Options DENY

X-Frame-Options helps prevent clickjacking, as does the frame-ancestors setting above. This option is obsolete, but so what.

Referrer-Policy

Header set Referrer-Policy no-referrer

With this Referrer-Policy setting, if someone follows a link from my site to another there is no header passed along telling the destination site that they came from here. This could be needed in some situations, but not here.

X-Content-Type-Options

Header set X-Content-Type-Options nosniff

X-Content-Type-Options tells the browser “not to load scripts and stylesheets unless the server indicates the correct MIME type.” It’s nosniff because it stops the browser from content sniffing.

Cross-Origin-Resource-Policy

Header set Cross-Origin-Resource-Policy same-origin

Cross-Origin-Resource-Policy “lets websites and applications opt-in to protection against vulnerabilities related to certain cross-origin requests.” Setting it to same-origin means “limits resource access to requests coming from the same origin. This is recommended for URLs that reply with sensitive user information or private APIs.” I’m not doing anything that even needs to worry about this, so I set it to the most secure option, because it will never matter.

Strict-Transport-Security

Header add Strict-Transport-Security "max-age=31536000; includeSubDomains; preload"

Strict-Transport-Security is an important one: it says that the site should only ever be accessed with HTTPS. The age setting is in seconds, and it equals one year. I don’t know if it matters any more, but you can add your domain to the HSTS Preload List as well.

Other sites

It’s fun to check how other sites rank at the HTTP Observatory. Right now proton.me gets 75/100 (!), gmail.com gets 105/100, wordpress.com gets 25/100, cbc.ca gets 5/100, and York University Libraries, where I work, gets 0/100. And if you can run curl at a shell, try looking at the headers of some sites you visit.