· Web Development

How we built a weekly security digest on a static site

A static site can do more than serve pages. Here is how this one writes its own weekly security round-up, with no server and no database.

People hear “static site” and picture a brochure: a handful of pages that never change. That is true of what gets served to visitors. It says nothing about what can happen before the site is built.

This site is a good example. Every Monday it writes a security round-up for the stack we look after (WordPress, Rails, SilverStripe, PHP and databases), drafts it as a blog post, and asks a human to check it before it goes live. There is no server, no database and no CMS behind it.

The idea

We keep other people’s sites patched and running, so we read security news anyway. Writing it up each week is useful to clients, and it shows the work. The problem is that doing it by hand every week is the kind of job that gets skipped.

So we asked a simple question: how little would it take to automate the dull part and keep the judgement?

How it fits together

There are only four moving parts.

  • A Ruby script. One file that collects the week’s news and writes a Markdown post. It pulls WordPress vulnerabilities from Wordfence, Ruby gem advisories from GitHub, SilverStripe advisories from Packagist, release announcements from the project feeds, and end-of-life dates from endoflife.date.
  • A scheduled GitHub Action. It runs the script every Monday morning.
  • A pull request. If there is anything new, the Action opens a PR with the draft post. Nothing is published automatically.
  • The normal deploy. Merging the PR to main builds the site and deploys it, exactly like any other change.

The schedule and the pull request are only a few lines of workflow configuration:

on:
  schedule:
    - cron: "0 7 * * 1" # Mondays, 07:00 UTC
  workflow_dispatch:     # or run it by hand

steps:
  - run: ruby bin/security_digest.rb
  - uses: peter-evans/create-pull-request@v7
    with:
      add-paths: |
        _posts/
        _data/digest_seen.yml
      branch: digest/${{ steps.date.outputs.date }}

If the script writes nothing, there is nothing to commit and no pull request is opened.

That is the whole system. The “database” is a flat file in the repository that remembers which items have already been reported, so a vulnerability never appears twice.

A few decisions worth borrowing

Keep a human in the loop. The script drafts and a person publishes. It costs a couple of minutes a week and removes the risk of something odd going out under our name.

Filter for relevance. The vulnerability feed for WordPress runs to hundreds of entries a month, most of them for plugins nobody we work with has installed. A short watch list of the plugins and packages we actually run turned a firehose into something readable. Most automation projects get better by saying less.

Let each source fail on its own. If one feed is down, the script says so and carries on with the rest. A digest missing one section is far better than no digest. The pattern is a rescue that logs a warning and returns an empty list (simplified here):

def fetch_releases(window)
  FEEDS.flat_map do |name, url|
    parse_feed(name, http_get(url)).select { |r| window.cover?(r.date) }
  rescue StandardError => e
    warn "WARNING: #{name} skipped (#{e.message})"
    []
  end
end

Store state in plain files. A tiny YAML file does the job of a database. Each line is an item we have already reported and the date we first listed it. It is versioned in Git, so every change is visible in the PR and can be undone.

"eol:rails:8.0": 2026-09-07
"wordfence:d20b2d00-054e-4772-a5a5-b7b33063043c": 2026-09-07
"https://mariadb.org/mariadb-13-0-is-now-stable/": 2026-09-21

Read the licence. Some data sources, Wordfence included, require attribution and a link back to each record. We built that into the output rather than treating it as an afterthought.

What it cost

One Ruby file, using only the standard library, plus one workflow file. A run takes a couple of minutes of CI time a week, a small fraction of what the free allowance provides. There is nothing to host, patch or monitor, which is rather the point for a company that sells exactly that service.

Ideas for your own site

The same pattern, a script that runs on a schedule and proposes a change as a pull request, fits plenty of jobs that are usually done by hand:

  • checking every external link on the site and reporting the broken ones
  • pulling a changelog from release notes
  • refreshing prices, opening hours or a team list from a spreadsheet
  • generating a monthly uptime or performance summary

If you have a site that feels like it should do something clever but you do not want to run a server to do it, get in touch. You can see what this one produces in the security notices.

← All posts

Or get in touch directly

How would you like a reply?

You’ll get a reply within one working day.