Today I wanted to add a security.txt to my blog. security.txt … what’s that? This file was specified in “RFC 9116: A File Format to Aid in Security Vulnerability Disclosure”. It’s a file placed in the .well-known directory of a web server that contains contact information for security matters. Since you don’t have to hunt around the website for the right contact1, it’s much faster to reach the responsible person. It seems like not many blogs implement this file. At least the ones I checked didn’t have one. But I think this is really useful information.

Now, you have to work around the fact that Jekyll ignores directories starting with a . by default. Jekyll simply doesn’t copy them into the _site directory. And that means they won’t appear on the web server either.

I know of two ways to get around this: Either you add the following to your config.yml:

include: 
  - .well-known 
  - .htaccess

Or you let Jekyll generate the file. I did this with the following _pages/security.txt:

---
layout: none
permalink: .well-known/security.txt
---
Contact: mailto:joerg@c0t0d0s0.org
Expires: 2027-04-14T00:00:00Z
Preferred-Languages: de, en
Canonical: https://www.c0t0d0s0.org/.well-known/security.txt

The permalink instructs Jekyll to generate the file at the correct location.

I went with the template method. Because I know myself. A year from now, I’m guaranteed not to remember to push the Expires: line one year into the future. This matters because files with an expired Expires: date are to be discarded.2

But hey, we have computers and computers can automate stuff. So I generate a security.txt with an Expires date one month in the future on every build.

Since it would be an absolute nightmare for me to implement a date one month in the future in pure Liquid — taking into account little details like the fact that there’s no February 31st (meaning simply month+1 doesn’t work) — I moved that into a Jekyll plugin.

So on my system, _plugins/in_a_month.rb contains the following:

module Jekyll
  class InAMonthTag < Liquid::Tag
    def render(context)
      (Date.today >> 1).strftime('%Y-%m-%dT00:00:00Z')
    end
  end
end
Liquid::Template.register_tag('in_a_month', Jekyll::InAMonthTag)

I then use the newly defined tag {% in_a_month %} in place of the fixed date. After that, _pages/security.txt looks like this:

---
layout: none
permalink: .well-known/security.txt
---
Contact: mailto:joerg@c0t0d0s0.org
Expires: {% in_a_month %}
Preferred-Languages: de, en
Canonical: https://www.c0t0d0s0.org/.well-known/security.txt

The blog is currently being run through the build process automatically on a regular basis anyway, due to the comment system running via Mastodon.3 This ensures that a new security.txt is generated regularly.

Should this build process stop running, it probably doesn’t matter whether the security.txt is expired anyway.

Note that this method requires custom plugin support. So this won’t work on GitHub Pages. There, you’ll have to build something equivalent using an Action.


  1. Only to find some generic alias that probably has an AI chatbot behind it by now. 

  2. See sections 2.5.5. and 5.3 in the RFC. Though in practice, “expired” is probably better than “nothing.” 

  3. To keep the load on the Mastodon server I use for this manageable, only the five newest toots are always processed, plus one third of all toots used as comment anchors. This means all toots are regenerated every three hours, as with an hourly build it’s still ensured that comments appear within 3 hours. 

Mastodon · Comments
No comments yet.
Be the first to reply! ↗
Written by

Joerg Moellenkamp

Grey-haired, sometimes grey-bearded Windows dismissing Unix guy.