Adding a Guestbook And Its Risks
Adapting a guestbook from Sappho Sys and its quiet risks of opening web comments to strangers.

Preamble
The web is an open horizon. Without interaction, a personal site can feel like talking to a void… or a rubber duck. I implemented comments to allow Bluesky users to leave their mark under certain articles, but that still meant requiring an account from another social platform. I wanted another way, something that didn’t ask visitors to be anywhere but here. That’s when the idea of a guestbook resurfaced.
Traditionally, guestbooks were paper ledgers where visitors would leave a note when entering an establishment–such as a church or an inn. On the web they serve the same humble purpose: a space for wanderers to leave a public note simply because they stopped by.
Luckily for me, Sapphic Angels had already built a beautiful and simple implementation. I wanted to bring a similar experience to my own site, built on the same architecture so I took direct inspiration from their work. But building a guestbook also meant asking: what are the risks of inviting strangers to scribble on your corner of the web?
Recreating the Guestbook
Understanding Cloudflare
Before diving into the components, it helps to understand where everything lives. This site runs entirely on Cloudflare Workers, a serverless platform that executes code at the edge. I just deploy and it works.
Within that ecosystem, I use three Cloudflare services:
- Workers: runs my API routes and renders server-side pages.
- D1: a managed, serverless SQLite database. Perfect for low‑traffic storage.
- Turnstile: a privacy‑friendly CAPTCHA alternative that verifies humans without annoying puzzles.
Everything is stitched together with the Astro framework, which natively supports Cloudflare as a deployment target.
Guestbook Components
I didn’t want to bother with a traditional backend, so I ended up with four main pieces:
- Cloudflare D1: a serverless SQLite database that costs nothing at this scale.
- Astro endpoints: API routes running as Cloudflare Workers. A single
GETfor listing, aPOSTfor signing. - React island: the sign form is a client‑side component (
client:only) that handles Turnstile and submission. - Cloudflare Turnstile: CAPTCHA‑free bot protection that does its job quietly.
During submission, Turnstile verifies a user’s authenticity, the worker sanitizes and stores the message, and the listing page fetches live data on every request.
Handling Spam & Bots
I knew from the start that a public form may introduce spam and abuse.
Before anything else, the client must solve a Turnstile challenge. The token is sent to Cloudflare’s verification endpoint from the worker; if it fails, the request is rejected immediately. This eliminates 99% of scripted bots without forcing a real visitor to click traffic lights.
After Turnstile, submissions from an IP address in the last five minutes goes through the guestbook_submissions table. If any recent submission exists, the request is blocked with a 429 response.
A human spammer might wait out the rate limit and submit the same message again. To prevent that, I hash the message content with SHA‑256 and check if the same IP posted the same hash within 10 minutes. If so, the submission is rejected as a duplicate.
These combined measures mean that a spammer would need fresh IPs, fresh messages, and the ability to solve Turnstile every time–an effort rarely worth it for a small personal site. These security measures are very rudimentary and is very easily bypassed if they’re motivated enough.
Security Concerns
SQL Injection & XSS
I wrote raw SQL with no ORM, but every query uses parameterised statements (? + .bind()), so SQL injection is impossible (to my knowledge). On the output side, a single regex (/<[^>]*>/g) strips HTML tags from names and messages. Because Astro and React escape content inside {}, even if something slips through, it won’t execute.
Content Moderation
Turnstile stops most bots, but a determined human can still post unwanted content. For a personal site with low traffic, I decided against a full moderation queue–instead, I rely on:
- Rate limiting: No more than one submission per IP every 5 minutes.
- Duplicate detection: The same message hash from the same IP within 10 minutes is rejected.
- Crypto‑backed hashing: Content is hashed with SHA‑256 so I never store the raw message a second time.
If spam still gets through, the IP is stored temporarily in a separate table, so I can easily block it at the server level.
Privacy
IP addresses are personal data. Initially, I stored them permanently in the entries table, a privacy risk I hadn’t fully considered. Now, the guestbook_entries table no longer holds IPs; the submissions table keeps them only for the rate‑limit window (auto‑cleaned after 24 hours). This felt like the minimum viable responsibility for a public guestbook.
At the Palm of the Vendor
If Cloudflare changes pricing, deprecates D1, or has an outage, the guestbook stops working. The code is portable and any SQLite‑compatible backend would work, but the serverless glue is proprietary. I mitigate this by keeping the business logic simple and agnostic.
Risks of Opening a Public Space
Further Spam and Abuse
Even with protections, a motivated person can still send distinct messages from different IPs. By design, there is no moderation queue to keep the module organic and spontaneous.
If the guestbook gets bigger and grows more popular, I’ll need a report system or a dashboard. For now, I’ll rely on the obscurity and tiny size of my audience.
Neglect
An interactive feature is a maintenance burden, no matter how small. I’m accepting that I must occasionally check for spam, prune garbage, and make sure that this thing remains a safe space.
Learnings
Serverless is Light, but not Weightless
D1, Workers, and Turnstile are all essentially free for a project this size. The whole guestbook runs without needing a traditional server, and I never think about scaling or uptime. However, there may come a time that the hidden costs will catch up.
Turnstile is Easy to Implement
The client renders a widget, the server verifies a token with a single API call, and the whole flow feels invisible to real users. Combined with rate limiting, it’s a simple anti‑spam prevention.
Results
Overall, building this guestbook was a fun exercise in stitching together modern serverless tools whilst blending it old-web nostalgia. The result is a tiny, fast, and maintainable feature that fits right into the spirit of the archaic and free-spirited websites of eld.
The risks of an open space in the internet are always in my considerations, but we’ll see if it’ll be manageable.
You’re welcome to leave a note on the guestbook. Please be nice!
Bluesky comments aren't configured for this article