Complying With the Latest Security Policies

Table of Contents

Modern websites and modern browsers support a wide range of security features to communicate specifically what is and is not allowed to be loaded, executed, or sent over the network. Being the person that I am, I’m going to comply with the latest guidelines and best practices as much as I can… and it’s a headache.

Content Security Policy

The biggest one has to be the Content Security Policy, which is giant list of “you can load X from Y”. Before showing my CSP, here’s a quick run-down:

You can specify controls for JavaScript, stylesheets, images, fonts, WebSocket / XHR connections, other non-image media, objects, embeds, & applets, prefetch / pre-rendered content, child content, <iframe> targets, Worker sources (not the Cloudflare type), domains allowed to <iframe> the site (see: X-Frame-Options), allowed <form> action targets, upgrade HTTP requests for resources, block HTTP resources on HTTPS page, allowed document base URI, web manifests, and allowed plugins.

While most can take a list of domains, they can also take * meaning “allow all”, self and none should be pretty self-explanatory, and.. well the rest I’ll get to in a second.

And yes, I know that disclosing security details is usually a bad thing, but it’s also sent over on every request, so it’s not exactly private knowledge.

Finally, this has been formatted for ease of reading:

default-src
  'none'

script-src
  'self'
  'unsafe-eval'
  'sha256-4ExdAblVQS3vP9+dOQPogQfTvVY/B4KGnQWH42NhRg0='
  'sha256-CmjHf3aA+ooXJwVib2vComMSJWhXsR6tbZ+gj+s3zSU='
  *.algolianet.com
  https://ajax.cloudflare.com:443
  https://static.cloudflareinsights.com:443
  https://cdnjs.cloudflare.com:443
  https://cdn.ko-fi.com:443
  https://ko-fi.com:443
  https://c.disquscdn.com:443
  https://disqus.com:443
  https://teknikaldomain-me.disqus.com:443
  https://www.gstatic.com:443

style-src
  'self'
  'unsafe-inline'
  https://fonts.googleapis.com:443
  https://c.disquscdn.com:443

img-src
  *

font-src
  'self'
  https://fonts.gstatic.com:443

connect-src
  'self'
  *.algolianet.com
  *.algolia.net
  https://links.services.disqus.com:443

media-src
  *

object-src
  'none'

child-src
  'none'

frame-src
  https://disqus.com:443

frame-ancestors
  'none'

manifest-src
  'self'

prefetch-src
  https://c.disquscdn.com:443
  https://disqus.com:443

upgrade-insecure-requests

block-all-mixed-content

default-src

Put simply: block everything unless explicitly allowed. I could have gone with self just in case, but… eh if every tool complains about self, I’ll just use none.

script-src

You’re likely not happy with this one. The entire reason I need to allow the use of eval() and those two hashes are because MathJax, the thing that allows me to write $\sum_{i=0}^n i^2 = \frac{(n^2+n)(2n+1)}{6}$ and get this: $\sum_{i=0}^n i^2 = \frac{(n^2+n)(2n+1)}{6}$. The hashes mean that only a script with that exact cryptographic hash is allowed, instead of allowing inline JavaScript. Luckily since there’s no unsafe-inline (because hashes are used), you can’t inject scripts.

I also need to allow gstatic for something cool: Google Charts. And the wildcard on Algolia’s… well I’m not whitelisting their individual domains. Algolia is the search engine that powers the search bar in the topnav, and trust me, it’s not tracking you. All it has access to are the characters you type into that box so it knows what to give back in search results.

Fun fact: I allow Cloudflare insights (browser timings) to see if there’s something that I can do to improve performance, it’s completely aggregate data that cannot be linked to a single person, feel free to block it if you want, I just want to make sure there isn’t some obvious slowdown somewhere. Also note that this also blocks Google Analytics, which is indeed somewhere in the site’s theme files (that I have yet to remove, working on it), but that also means that yeah, there’s no GA tracking because your browser will just complain about a CSP violation until I excise it. Nice.

style-src

Also weird… again, MathJax puts style attributes in everything, meaning I need inline styles. Not as bad as inline scripts, but still a little insecure.

And yes, a stylesheet is loaded from the fonts domain. I don’t know.

img-src

Basically, just allow all images. Because of the last two directives, everything is forced to be HTTPS only, and I don’t know ahead of time what sites I might pull images from.

font-src

Fonts… self-explanatory.

connect-src

This allows WebSocket and XMLHttpRequest connections to be made. ‘self’ is likely not necessary, but a few things here use WS for communication, though they really shouldn’t in production. And the Disqus URL is for.. Disqus.

media-src

Non-image media, like videos, audio, whatnot. Heck with it, just include it all.

frame-src

What sites are we allowed to include in a <frame> or <iframe>? Disqus needs it.

frame-ancestors

What sites can use us in a <frame> or <iframe>? Nobody.

manifest-src

Where to pull a manifest file from. In this case, it’s site.webmanifest, which is this:

{
    "name": "TeknikalDomain.me",
    "short_name": "TeknikalDomain.me",
    "icons": [
        {
            "src": "/android-chrome-192x192.png",
            "sizes": "192x192",
            "type": "image/png"
        },
        {
            "src": "/android-chrome-512x512.png",
            "sizes": "512x512",
            "type": "image/png"
        }
    ],
    "theme_color": "#1da82d",
    "background_color": "#1da82d",
    "display": "standalone"
}

All it really does is tell Android phones how to display everything when someone makes a shortcut to their launcher.

prefetch-src

Domains that you can prefetch or prerender content from before it’s required, saving a little bit of load time. Disqus.. again.. uses this.

Side note: For a long time, these kept failing because prefetch-src doesn’t exist. I checked and there’s a message above, stating that “The Content-Security-Policy directive prefetch-src is implemented behind a flag which is currently disabled.”

The answer is chrome://flags/#enable-experimental-web-platform-features (for me), which needed to be enabled for the browser to recognize prefetch-src. Luckily content seemed to load fine once it was actually required, but the time-saving prefetch was blocked.

upgrade-insecure-requests

Any resource that has an HTTP URL will be automatically rewritten to HTTPS, no exceptions.

block-all-mixed-content

If a resource cannot be loaded over HTTPS, only HTTP, then refuse to load.

Cookies

I don’t actually use cookies on here.. at all. Cloudflare does provide a _cfduid cookie for.. whatever they’re doing.

if you’re doing cookies, make sure that all cookies are Secure (meaning that they aren’t transmitted on a plain HTTP connection), and any backend session cookies are HttpOnly (Scripts have no access).

Cross-Origin Resource Sharing (CORS)

CORS is a mechanism where one domain is permitted to load code or other resources from another domain. Normally, the web browser will block cross-origin requests like this unless it sends an Access-Control-Allow-Origin header, which specifies which domains are allowed to request a resource. Since I’m not sharing anything, just not adding the header is enough to prevent it.

Expect-CT

Certificate Transparency (CT) is, simplified, a public list of issued TLS certificates that can be checked by anyone. An Expect-CT header means that the browser is to cross-reference the CT database, and refuse to accept the certificate if it does not exist.

For example: the header my website sends is:

Expect-CT: max-age=604800, report-uri="https://report-uri.cloudflare.com/cdn-cgi/beacon/expect-ct"

Now this one is part of Cloudflare, and I don’t have access to it. what’s interesting is that it lacks an enforce directive, meaning the browser will report a CT failure, but will still load the page. max-age is just the number, in seconds, that the browser should remember to check CT for this domain.

Feature-Policy

I.. don’t have one of these yet and it’s still very experimental. Like a CSP, a feature policy is a set of directive with an access list specifying what’s allowed to use them. Unlike a CSP, a FP is the set of policies for browser features, such as video autoplay, getting your device’s accelerometer data or battery status, use the microphone, there’s an entire list of these, which I won’t really get into right now, but suffice it to say that one acts like a CSP for browser features instead of resource types.

HTTP Public Key Pinning (HPKP)

Deprecated but I’ll still cover it. HPKP was presented in the form of a Public-Key-Pins header, which would specify the SHA-256 hash of the key that the website’s certificate was using. You’re allowed to specify multiple, and had to specify at least two (one backup) for it to be honored. Since the spec meant that you could pin any key in the chain, pinning your end certificate and then the intermediate certificate as a backup was what a lot of people would do.

The issue with HPKP was that, besides needing to constantly update headers, you could lock everyone out of your site if you pinned keys incorrectly, and HPKP has been deprecated in favor of Expect-CT.

HTTP Strict Transport Security (HSTS)

HSTS is a mechanism where the browser is required to transparently redirect any HTTP requests to that domain to HTTPS. The HSTS header, Strict-Transport-Security, specifies up to three things: the max-age, subdomain inclusion, and preloading.

max-age is just the number of seconds where the browser needs to remember that this site is HSTS. Once this amount of seconds elapses, plain HTTP requests are allowed (usually resulting in a redirect to HTTPS which resets HSTS). For security, you want a long max-age, I set 31536000. 31,536,000 seconds is equivalent to 525,600 minutes, or 8,760 hours, or 365 days. Yes, you need to not touch my website for an entire year before your browser is even allowed to think about making plain HTTP requests again.

I also send includeSubDomains, meaning that any subdomain of teknikaldomain.me (say, forum.teknikaldomain.me for example) is also included in HSTS.

HSTS Preloading

Most browsers have a built-in (literally hard-coded source code) HSTS preload list, which is a list of domains that are known to use HSTS, and the browser should consider as always HSTS.

The site hstspreload.org can be used to query or submit sites to the list.

There’s a few requirements for preloading though:

  • Your TLS certificate must be valid
  • HTTP redirects to HTTPS on the same host
  • All subdomains are HTTPS
  • HSTS header must specify a minimum time of 1 year, includeSubDomains, and a preload directive, which specifies that the site is requesting inclusion in the list.

You must continue to meet these requirements, or it’ll get removed.

Also changes to the list may only take place every couple months, since the list is actually hard-coded source, it only updates every major browser build.

Referrer Policy

When redirected from one link to another, browsers will send a Referersic header, indicating the URL that it just came from. For security reasons, this isn’t always good practice, which is why we have Referrer-Policy, and it’s spelt correctly this time.

RP can take on only a few values, which I’ll go through one by one:

  • no-referrer: Don’t send Referer.
  • no-referrer-when-downgrade (default): HTTPS -> HTTP connections omit Referer, the rest keep it.
  • origin: Only send the origin (e.g., send http://example.com/ for http://example.com/page.html).
  • origin-when-cross-origin: Send just the origin when going to a different site, and the entire thing when staying on the same site.
  • same-origin: Only send header to the same domain, omit on cross-domain.
  • strict-origin: Only send over HTTPS.
  • strict-origin-when-cross-origin: Send everything when going to the same site, and only the origin to other sites. Nothing is sent over plain HTTP.
  • unsafe-url: Send everything, regardless.

I use strict-origin-when-cross-origin, so as long as you’re going from one page of teknikaldomain.me to another page of teknikaldomain.me, it’ll know what you did. But if you clicked off a link to a different site, all it will know is that you came from teknikaldomain.me but not exactly where. Additionally, if you’re visiting another site over plain HTTP, no header is to be sent at all.

Server Header

The Server header isn’t a real security header, but it can be a security risk. Usually the web server that’s… serving the content will fill this in, say with like Server: nginx or something. Knowing the backend server in use is actually a security leak, since now an attacker knows what you might be vulnerable to. Cloudflare automatically masks this with Server: cloudflare, so it’s pretty useless for determining anything.

Any value here really works as long as it does not give away the actual program that’s doing the handling, like Apache, Nginx, Caddy, or IIS. You can still use Server to indicate, say, which datacenter a request was routed to, got debugging. Just don’t stick program names in there.

Subresource Integrity (SRI)

The one that… I literally cannot do. For resources, like JavaScript, that are loaded from an external domain, there’s no real guarantee that you’re loading the correct thing. This is why, for example, the <script> tag has an integrity attribute, which specifies the cryptographic hash of the resource. As such, if the hashes do not match, the browser will refuse to load what it got in response. Essentially, you’re saying that this resource and only this resource are allowed, regardless of what the name says. The issue is that for SRI to work, resources need the proper CORS header, and of the two external resources I load, neither do. Given that they’re both resources from CDNs, the fact that they don’t is confusing in its own right, actually. Furthermore why don’t browsers reject it then?! Regardless, what that means is that I could add in an integrity attribute to the scripts, but the browser would refuse it because the resource domain isn’t authorizing CORS requests.

X-Content-Type-Options

Servers usually send a Content-Type header to the browser, telling it the MIME type of what it just requested. For example, asking for test.png will have Content-Type: image/png on it.

XCTO only has one value, nosniff, which means that browsers should, get this, respect the Content-Type header and not try to deduce the content type themselves. The need arose when ’type sniffing’ (automatic file type detection) was turning non-executable types into executable types back in the early days of the web.

Either way, why do we need to tell browsers to literally just not tamper with a value they’re already given‽

X-Frame-Options

Superseded by CSP’s frame-ancestors directive. Essentially, when a browser loads a page through an <embed>, <iframe>, or similar mechanism, it checks the XFO header, if any. I set it to DENY, meaning no attempt to frame the page will succeed, and the browser should refuse to load like that. Alternatively, you can use SAMEORIGIN meaning that you can only be framed by yourself.

X-XSS-Protection

Most modern browsers have a cross-site scripting (XSS) auditor that will attempt to block what it thinks is XSS, for example, someone leaving a comment that is just some <script>.

The header can have a base value of 0 or 1. Zero means to not protect (why?), and one means to ask the browser to enable XSS protection. If any is detected, it will be deleted. If mode=block is added, then instead of deleting it, the browser will just fail the page load, and complain that due to XSS it’s not going to.

Like most things, it’s a little obsolete now, especially with CSPs in place.