Colophon

How this site is built

This site is part of my work too, and you can check it the way you'd check any of my code. This page follows the code: if the two disagree, the code is right and this page has a bug.

Stack

Plain files and one Worker

No framework, no build step.

HTML and CSS written by hand, and one small vanilla JavaScript file in the browser. No framework and no build step: what's in the repository is what gets served.

A Cloudflare Worker written in TypeScript stands in front of the pages. It decides which pages are on, fills in the texts from a D1 (SQLite) database, builds the sitemap, and runs the admin and the contact form. Styles, scripts, images and icons skip it and go straight out.

The database holds the page texts, the photo, the menu settings, messages from the form and an event log. Mail from the site goes out through Cloudflare Email Routing.

Type from Google Fonts: Newsreader for headings and reading, Public Sans for the interface, IBM Plex Mono for data.

Security

Headers and a locked door

Strict rules for the browser, a closed admin.

Every page carries a strict Content Security Policy: no inline scripts, and no third-party ones except the bot check on the contact and order forms. Plus HSTS, a ban on framing, and the rest of the headers any scanner looks for.

The admin lives at a separate address. Signing in takes the password (stored as a PBKDF2 hash), a code from an authenticator app and a Turnstile check. Attempts are limited for each network and overall, and a wrong password and a wrong code get the same answer. A session is kept in the database: signing out really ends it, and it ends by itself after twelve hours, or after two hours without use. Changing the password or the sign-in settings asks for the password and a code again. Every sign-in attempt and every change goes into a log.

An uploaded photo is checked by its first bytes, not its file name, and served in a sandbox. Secrets live only in GitHub's secret store; the pipeline hands them to the Worker on deploy.

How to report a problem →

Tests and pipeline

Tests with teeth

Nothing reaches the site without checks it could have failed.

Before every commit and push, the code goes through strict TypeScript types, a linter, formatting, a dead-code search, unit tests, property tests (fast-check) that run functions over hundreds of generated inputs, and page tests in a browser emulator.

Mutation testing: Stryker breaks the Worker's logic (security, form checks, texts, page settings) about a thousand ways, and the tests have to catch at least 80% of the breaks. Right now they catch 97%.

Commits are signed. Hooks refuse commits straight to main, force pushes, secrets and large files. The site changes only through a pull request, which scans the whole history for leaked secrets (gitleaks) and audits the dependencies (npm audit), the code (Semgrep) and the pipelines themselves (zizmor). After the merge: database migrations, deploy, and a check of the live site. Once a week the whole thing runs again from scratch.

Limits

What this site can't do

An honest list.

Hide your visit from Cloudflare, which serves the site, or from Google, which serves the fonts.

Make the words true. A strict security policy limits what a compromised script could do; it says nothing about the text.

Show you the source: the repository is private for now. Or point to an independent security audit: there hasn't been one.

Privacy

No cookies for visitors

No analytics, no ads, no trackers.

The site sets no cookies for visitors. There are only three, and all are mine: the admin session, a mark that this browser is mine, and a marker that turns the footer's links into “Dashboard · Sign out”.

No visit counters, no analytics, no ads, no trackers.

The contact form stores your message in the database and emails it to me. Your IP address isn't stored, only a hash of it with a secret salt, used to limit messages per hour. The bot check (Cloudflare Turnstile) loads only when you start filling in the form.

The full privacy notice →