Bilingual personal site built with Astro: subnet calculator, hash generator and more — the tools run in the browser, and the little that genuinely needs a server lives in an isolated Cloudflare Worker, kept apart.
Bilingual by construction: content (markdown/JSON) lives separate from the code and feeds both versions (PT/EN) from the same shared components — no duplicated logic between languages.
Security shaped the design from the start: strict Content-Security-Policy with no ‘unsafe-inline’, security headers, and a published responsible-disclosure policy. The why behind each layer is on Security; the proof — commit, live header scan, workflows — is verifiable on Evidence.
Architecture decisions
Why Astro with no client-side framework. No React/Vue/Svelte by
default — pages ship with zero JavaScript, and the islands that need
interactivity (the networking tools, the Lab) load no hydration runtime at
all. That’s not only a performance choice: it keeps the strict CSP with no
'unsafe-inline' easy to maintain, because there’s no framework injecting
inline style or script at runtime invisibly — and because I write my
own <script> tags as external files, never inline, script-src 'self' and
style-src 'self' cost nothing, no hash needed. (I tried the reverse first —
SHA-256 hashes per inline script/style — but the hash count grows with the
page count, and after a few dozen pages the Content-Security-Policy blows
past the 2000 characters Cloudflare Pages allows per header line; removing
the inline code instead of cataloguing it fixes the root cause.)
Why the Worker is separate from the static site in the monorepo. The
static/ site (this one) keeps the threat model described on the
Security page as simple as possible: no backend, no
database, no user input that reaches a server. The features that genuinely
need a server — the honeypot, the traffic map, the
Certificate Transparency watcher, the threat-intel ticker — live isolated in a Cloudflare
Worker (dynamic/worker/), published separately. That means the static site
keeps working (and keeps its “no backend” promise) even when the Worker is
down or not published at all — the sections that depend on it degrade
gracefully instead of breaking the rest of the site. The live result of
these layers is on Honeypot and
Cloudflare.