Post-mortem
One phishing page took down every site we hosted
A single user published a fake login page. Within hours, Google flagged our entire domain and the registry pulled it out of DNS, knocking hundreds of unrelated sites offline at once. Then we discovered we'd boxed ourselves into a catch-22 that kept us from fixing it. Here's what broke, the trap we walked into, and what we changed so neither can happen again.
boomurl is about the smallest useful thing a web host can be. You drag a file (an HTML page, a Markdown doc, a PDF, or a whole folder) onto the page, pick a name, confirm a code, and roughly 20 seconds later you have a real HTTPS URL. No account, no build step, no dashboard. It's the "just put this on the internet" button.
That simplicity is the point. It also turns out to be an open invitation to the internet's least pleasant people.
What happened
For most of boomurl's life, every published site lived on one domain, path-based: boomurl.site/alice, boomurl.site/bob, and so on. One afternoon someone used that 20-second flow to publish a convincing bank-login phishing page on it.
Google Safe Browsing noticed, as it should. But Safe Browsing, and the registry that acted on it, don't reason about /alice vs /bob. They work at the level of the registrable domain. So the flag didn't land on one path. It landed on the whole host:
Every user who had ever published (people with resumes, demos, wedding pages, side projects) suddenly got a full-screen red warning, then nothing at all once the domain stopped resolving. None of them had done anything wrong. The shared origin was the vulnerability.
If you host user content on one domain, you are always one bad page away from a platform-wide reputation event. We learned that the expensive way.
The part that actually hurt: a catch-22
The phishing page was bad. What made it a week-long problem instead of an hour-long one was the corner we'd painted ourselves into.
.site is run by a registry (Radix), and once Safe Browsing flagged us, that registry put boomurl.site on serverHold. That status yanks the domain out of DNS completely, so it stops resolving for anyone. Not a warning page. Gone. Our sites, our API, everything on that domain.
To get a Safe Browsing flag reviewed, you ask Google to re-check the site, and you do that through Google Search Console. To do anything in Search Console, you first have to prove you own the domain, normally by publishing a DNS record on it or serving a file from it. But the domain was on hold and out of DNS, so we couldn't publish a record or serve a file from it. And we had never verified boomurl.site in Search Console back when it was healthy, so we had no standing there at all.
Every door was locked at the same time:
- We couldn't clear the flag without Search Console.
- We couldn't get into Search Console without a domain that resolved.
- We couldn't get the domain resolving again without the registry lifting the hold.
- And the registry, reachable only through our registrar, went quiet. Days passed with no human on the other end.
That loop is the single most important thing to avoid, and it's the reason this article exists. The phishing page wasn't the real failure. Phishing is just a fact of life once you let strangers publish. The real failure was that the only way back online ran entirely through a support queue we didn't control, and we'd set up no fallback of our own ahead of time.
There was one free step that would have broken the whole loop, and we'd skipped it.
Verify every domain in Google Search Console the day you buy it, while it still resolves. It's free, it takes two minutes, and it's the one foothold that survives your domain going dark.
We did exactly that for the new domain before moving a single site onto it. If we're ever flagged again, the appeal channel already exists, verified and waiting, no matter what any registry decides to do. Boomurl.site, for the record, never came back. We stopped waiting on the registry and moved on.
The core mistake: shared blast radius
The diagram below sums it up. On the left is what we had: one origin, everyone's content mixed together, so a single flagged page reddens the entire surface. On the right is what a host that survives this looks like. Every site sits on its own origin, so a flag is contained to exactly one.
What we rebuilt
Getting everyone back online was the easy part. We removed the abusive content and moved serving to a fresh domain. The harder work was making sure this class of failure can't recur. We ended up with four layers, running from before a site is published to after we might get flagged anyway.
1. Every site is now its own origin
Published sites moved from host/<name> to <name>.host, a real per-site subdomain, each one its own web origin. A Safe Browsing flag, a rogue script, a cross-site cookie: all of it is now scoped to a single subdomain instead of the shared parent. It's the same isolation model that lets github.io, netlify.app, and pages.dev host millions of untrusted user sites without one poisoning the rest. We've also filed boomurl.me for the Public Suffix List, so browsers treat each subdomain as a fully independent registrable site.
2. Bad content never gets a URL in the first place
Before a site is stored or served, its content is scanned for phishing signals, and every outbound link is checked against Google's Safe Browsing blocklist in real time. High-confidence phishing gets rejected on the spot, so it never gets an address to abuse.
3. A scanner sweeps the whole estate every 15 minutes
Content can turn malicious after it's published, or link to a URL that only later lands on a blocklist. So a background job re-scans every site continuously and takes down high-confidence abuse on its own, within minutes, rather than whenever a human happens to file a report.
4. We watch our own reputation, so we hear it first
The worst part of the original incident was that we found out we'd been flagged from our own users. Now every scan also checks our own domains against Safe Browsing and pages us the moment Google's opinion of us changes. That turns what used to be a multi-day outage into a same-minute alert. We also registered with Search Console up front, so the appeal channel exists before we ever need it again.
How we shipped all of this in an afternoon
This next part matters if you run infrastructure. Everything above (cutting the serving domain over to a new one with zero downtime, standing up an edge worker for the per-site subdomains, adding scanner jobs that run around the clock, and redeploying the app more than a dozen times while people kept publishing) happened in a single working session.
That's possible because boomurl runs on Control Plane. The serving app, the Postgres database, the abuse scanners, and the domain migration itself were all just resources we could reshape while they ran. We changed serving behavior behind an environment flag, rolled forward and back without dropping a request, and never opened a Kubernetes YAML file to do it. In the middle of an incident, being able to change how the system works while it stays online is exactly what you need and rarely what you have. If you're building something that has to stay up while you change it, controlplane.com is worth a look.
Four things we'd tell anyone
- Isolate blast radius before you need to. If user content shares one origin, that origin is a single point of reputational failure. Per-tenant subdomains, plus the Public Suffix List, are cheap insurance you'll be glad you bought.
- Prevention beats cleanup. Screening at publish time, including a live Safe Browsing lookup of outbound links, stops the highest-confidence abuse from ever getting an address.
- Detection latency is a real number. A flag can propagate faster than a daily scan. Ours runs every 15 minutes and watches our own domains, so we find out first.
- Own the appeal channel in advance. Verify every domain in Google Search Console the day you buy it, while it still resolves. Once a registry pulls the domain from DNS you can't verify ownership anymore, and that's the exact moment you need to ask Google for a review. This is the one that cost us the most.
Put something on the internet in 20 seconds
Drag a file. Get a real HTTPS URL on its own subdomain. No account, no build step, free.
Try boomurl →This very article is a boomurl site. That's the whole product.
Questions, or want the deeper technical detail on any layer? We're happy to get into it. The isolation model and the classifier were the fun parts to build.