SHAZIN
Thumbnail for the article The Risk About Supabase's REST Exposure. The REST Flood: A BTS Look at Handling Abusive Request Flood on a Deleted PGSQL RPC
The Risk About Supabase's REST Exposure. The REST Flood: A BTS Look at Handling Abusive Request Flood on a Deleted PGSQL RPC

08 Jul, 2025

Shazin

8 minutes read

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?

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:

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.

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:

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:

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:


✅ Step 3: Route Everything Through a Cloudflare Worker

Now the frontend calls:

fetch("https://api.myappdomain.com/reward", { ... })

The Worker:

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:

So I did what anyone would do:

I stopped trying to protect the exposed endpoint.

Instead:


🔚 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:

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.