A behind-the-scenes look at how repeated abuse on a deleted Supabase RPC nearly brought down my backend, and what I learned about securing Supabase APIs under real-world pressure.
A Little Backstory
I was building a project using
Supabase
— a backend-as-a-service platform that provides an easy-to-use REST and RPC interface over PostgreSQL. One of my features included a
reward claim system
, implemented as a simple
reward()
function exposed through Supabase’s RPC endpoint.
Frontend was using the standard JS client:
await supabase.rpc("reward");
At first, everything was smooth. Auth was in place. I had simple SQL logic that validated the user’s eligibility before issuing rewards. I thought the system was secure.
Boy, was I wrong.
The First Sign of Trouble
A few days later, I noticed unusual server load. Logging into Supabase, I found something shocking:
Millions of calls to
/rest/v1/rpc/reward
— even though my logic was correctly preventing unauthorized rewards.
I assumed someone was brute forcing the endpoint — using valid JWT tokens (probably copied via browser dev tools) and hitting the endpoint through scripts.
My immediate response?
- Added timestamp checks to block repeat claims
- Tightened validation logic
- Added guards inside the SQL function
But despite all this… The traffic didn’t stop.
Realization #1: It’s Not About Logic — It’s About Load
Even though I was denying reward claims successfully, each request still hit the Supabase backend , and every request cost me CPU/network/database units or engress as they call it.
Supabase was:
- Validating the JWT
- Routing the request
- Returning a 403 or 404
But that still meant my server was under attack — not logically, but infrastructure-wise .
So I tried the next move…
I Deleted the
reward()
RPC
Thinking this would render the endpoint invalid, I removed the
reward()
function and replaced it with a new one:
reward_v2()
— and this time I routed all requests
through a Cloudflare Worker proxy
, protected with rate limiting and bot detection.
-
New users started using
reward_v2()
- Worker only allowed requests from verified, active users
- Everything seemed back under control
But the next day morning...
I checked my Supabase logs.
The original endpoint
/rpc/reward
was still getting hit millions of times per hour.
Even though the function no longer existed. Supabase just returned
404
,
but still processed the requests.
That’s when I realized:
Realization #2: Supabase Doesn't Let You Close the Door
Supabase does not let you:
-
Disable or firewall the default
project.supabase.co
REST/RPC API -
Change the
/rest/v1/
path - Change the rest api origin(domain/subdomain)
- Add IP allowlists or WAF rules to your Supabase project directly
Even after deleting the function, Supabase’s edge layer still parses the request and returns a 404, consuming resources.
I even tried recreating
reward()
just to raise an error instantly, hoping to reduce processing time — but the spam didn’t stop.
At this point, I wasn’t dealing with abuse logic. I was dealing with infrastructure-level abuse — something that Supabase currently doesn’t give you control over.
Realization #3: I Needed to Proxy Everything — Immediately
That’s when I made a firm decision:
All valid API calls must go through a WAF-protected proxy, and never directly hit Supabase again.
Here’s what I did next.
🔐 Securing Supabase Using Cloudflare Proxy + WAF
✅ Step 1: Enable Hardened Data API
In Supabase Dashboard → API → I enabled “Hardened Data API” , which:
- Requires valid JWTs for all REST and RPC calls
- Blocks anonymous and unauthenticated access (even with a leaked anon key)
This reduced some of the load by short-circuiting bad requests, but it didn’t stop the flood completely.
✅ Step 2: Configure a Custom Domain for Data API
Supabase allows you to expose the REST/RPC API on your own domain. I set:
api.myappdomain.com → CNAME to <project>.supabase.co
Through Cloudflare, I:
- Proxied this domain
- Attached WAF rules
- Added rate limiting
✅ Step 3: Route Everything Through a Cloudflare Worker
Now the frontend calls:
fetch("https://api.myappdomain.com/reward", { ... })
The Worker:
- Verifies user JWT
- Applies bot filtering
- Calls Supabase via service_role securely
- Forwards only valid requests
No client ever sees the true Supabase URL again.
✅ Step 4: Rotate Keys & Remove the Old RPC
I rotated the
anon
key, deleted the old function, and hardened every RPC with role checks like:
if auth.role() != 'service_role' then
raise exception 'unauthorized';
end if;
But the Spam Kept Coming 😡
Even with all this… The old Supabase domain kept getting hit :
https://<project>.supabase.co/rest/v1/rpc/reward → 404 → again → again → again
And Supabase’s backend still processed every one of those garbage requests and my engress kept increasing.
Final Realization: Supabase REST Exposure is Unpatchable (Today)
There’s no way to:
- Change the endpoint paths
- Disable the public REST API entirely
- Attach firewall rules to the default Supabase project URL
So I did what anyone would do:
I stopped trying to protect the exposed endpoint.
Instead:
- I moved all real traffic to the Cloudflare-protected custom domain
- I let the default domain take 404s — knowing it wouldn’t leak any useful data
🔚 Conclusion: What I Learned
Lesson | Insight |
---|---|
Supabase is great for fast MVPs | But lacks deep API-level security controls |
REST APIs should be proxy-only | You can’t expose them directly in hostile environments |
You need WAF + Rate Limiting | Especially if you're offering anything that could be gamed |
Hardened Data API helps | But doesn’t block abusive 404s |
You can’t rely on deleting RPCs | 404s still consume resources |
So what worked?
Well... Honestly nothing....I left the server is it is but the only solutions(even hypotheticals) are:
-
Changing project ID thus by changing the base REST api origin like
<project-id>.supabase.co
- Since changing id not possible, migrating the entire project and this time do not expose the rest endpoints instead proxy the requests
- If supabase added a feature in the future to guard their APIS through WAF kinda rules or bot detections.
Final Recap of The Story
SO I actually created a recap of the entire problem from day 1 to send a friend over email, to ask for suggestions. Here it is:
Problem: Attackers flooding rest endpoints with invalid requests
I was previously calling supabase pgsql rpc function through the JavaScript client using something like await supabase.rpc("reward") . Then I found out some users are abusing this. They are using scripts to send repeated requests to the rest endpoint <my-supabase-id>.supabase.co/rest/v1/rpc/reward
Initially I stopped the abuse using sql logic to prevent them from taking reward.
But they were still sending requests, so I removed the rpc function reward() I created another function reward_v2() and now instead of calling the function from client I started calling it through a proxy by cloudflare with proper bot prevention and WAF rules.
At this point I thought, my server is secured. Then this morning I saw the rpc calls number and got astonished.
Millions of calls to the old rest endpoint
<my-supabase-id>. supabase.co/rest/v1/rpc/reward
All with a 404 response but this was causing overload to my server and increasing REST request number.
Next I tried to recreate the function reward() by just raising an error directly just to reduce the cpu usages
But that didn't work either. The requests kept coming through.
Now I am here after all the error handlings with 2 possible solutions
1. Either change the rest api url to something else I mean changing the origin/domain so that attackers cannot flood my server with bad request, since my endpoint is already exposed. But supabase doesn't allow that.
2. Add some sort of rate limitings, unfortunately supabase doesn't allow that.
I don't care about the RLS security rules or any database protection techniques, since the attackers are just trying to flood the server so I need to somehow change/limit the endpoint. But I seem to find no ways.
Final Thoughts
Supabase is an incredible tool — but for production environments, you need to take API security and infrastructure protection into your own hands .
I hope this post helps others avoid the panic, frustration, and cost of discovering these limitations the hard way — like I did.
So Please Guard Your Supabase API Endpoints and Proxy it, Or Use SSR. Also, If anyone from supabase is reading please introduce a feature on guarding the api endpoints from abuse like this. Add ways to guard exposed API to put s proxy before the API endpoints.