Today, I want to share a link to an article that covers using CloudFlare workers to add security headers to content served from an origin that won’t allow you to set such headers naturally, such as GitHub Pages or AWS S3.

These security headers are effective methods of preventing injection attacks against your website, or other websites leveraging your website.

In this great article by Scott Helme, he introduces code to create a CloudFlare worker that will add headers to your website. I recommend giving it a read to understand how the code works and how to set up a CloudFlare Worker to add headers to your site. Scott also has a site that’s useful to test that your CloudFlare Worker is properly setting headers.

Be very careful when setting the HSTS header. If your origin is not serving content via TLS, or if you decide to revert from HTTPS to HTTP, this will cause your site to become inaccessible to users for the duration of the HSTS max-age value which is typically set for one year.

Beware that setting HSTS will make it difficult to revert to HTTPdifficult to revert to HTTP.

Here’s the code for the worker serving this site:

let securityHeaders = {
    "Content-Security-Policy" : "upgrade-insecure-requests",
    "X-Xss-Protection" : "1; mode=block",
    "X-Frame-Options" : "DENY",
    "X-Content-Type-Options" : "nosniff",
    "Referrer-Policy" : "strict-origin-when-cross-origin",
    "Permissions-Policy" : "geolocation=(), microphone=(), camera=(), payment=()",
}

let sanitiseHeaders = {
    "Server" : "Server",
}

let removeHeaders = [
    "Public-Key-Pins",
    "X-Powered-By",
    "X-AspNet-Version",
]

addEventListener('fetch', event => {
  event.respondWith(addHeaders(event.request))
})

async function addHeaders(req) {
  let response = await fetch(req)
  let newHdrs = new Headers(response.headers)

  if (newHdrs.has("Content-Type") && !newHdrs.get("Content-Type").includes("text/html")) {
    return new Response(response.body , {
      status: response.status,
      statusText: response.statusText,
      headers: newHdrs
    })
  }

  Object.keys(securityHeaders).map(function(name, index) {
    newHdrs.set(name, securityHeaders[name]);
  })

  Object.keys(sanitiseHeaders).map(function(name, index) {
    newHdrs.set(name, sanitiseHeaders[name]);
  })

  removeHeaders.forEach(function(name){
    newHdrs.delete(name)
  })

  return new Response(response.body , {
    status: response.status,
    statusText: response.statusText,
    headers: newHdrs
  })
}

Once you’ve saved and deployed your service worker code, all that’s left is to create a route to the worker from your CloudFlare Dashboard > Workers page. Mine routes *.chrislockard.net/* to point to the security headers worker and I can verify that all is well using curl or Security Headers!

One last thing I want to point out is that, since Scott’s original article was published, CloudFlare has provided a generous Free (gratis) plan for Workers, which as of this writing includes:

  • 100k requests/day (excess requests return errors)
  • Up to 10ms CPU time per request
  • Up to 30 workers

For a small site like this one, this plan is perfect!