I was wrong about protecting Supabase with Cloudflare. The docs say why.
I was about to buy a custom domain to put Cloudflare in front of my Supabase project. Then I read the documentation properly and found the line that killed the plan.
When you set up a custom domain, the original project URL stays live. The docs say you can use both interchangeably. And the project ref isn't a secret — it ships in the browser bundle, because that's how the client connects in the first place.
So the proxy is optional for an attacker. Open devtools, read the original hostname, talk to the database directly. I'd have paid monthly for something that protects the URL in the address bar and nothing else.
The uncomfortable part is what that implies more broadly. The entire appeal of this architecture is the browser talking straight to the database. Which means every request that matters — login, reads, writes, uploads — never touches my application server. A WAF in front of my app protects my landing page.
What actually helped, all inside the platform, none of it code:
CAPTCHA in Auth. It validates inside Auth itself, so there's nothing to route around. That's the real defense against mass signups, not the edge.
Auth's native per-IP rate limiting, which I had wrongly written off in my own earlier notes. It exists, for auth endpoints. There's an open issue suggesting the configured value isn't always honored, so I treat it as a floor rather than a setting.
RLS as the only actual barrier, since the anon role holds table grants by default.
Size and MIME limits per storage bucket. Mine were empty, meaning any file type was accepted.
The gap I haven't solved: PostgREST has no native per-IP limit. max-rows and RLS reduce the blast radius, not the request count.
For anyone running this architecture in production with real traffic — what did you do about that last one?
Replies