Don’t Expose Your Customer Data With Supabase

Video Of This Blog Article

Supabase has become wildly popular recently with front end developers, from web developers using React or Vue etc, mobile app developers and probably especially low/no-code app developers all looking for an alternative to Google’s Firebase.

What’s not to like? It’s really pretty amazing how quickly you can get a backend for your app spun up merely by modeling some data with their very well designed studio. When you’re done, your data api is done and you get Authentication and Storage and even cloud functions available to you from a single provider.

But, there’s a security issue I’m concerned about. It’s a big one.

Developers that prefer SQL over NoSQL solutions love Supabase as they can stay where they’re comfortable, as Supabase is powered by the venerable PostgreSQL which is clearly trusted by tons of developers around the world.

Supabase is even open source! You can develop locally or if you want to roll your sleeves up, you can host this solution yourself, though I would imagine the vast majority currently are leveraging Supabase’s hosted cloud offering. They even have a free tier to get started at the time I write this.

Those that know me, know I generally keep to myself. You can’t let the engineers talk to the damn customers, after all. I’m historically a consumer of content but don’t produce it. Perhaps I need to change this, after this experience. Kinda hard to get the word out on something if you don’t have an audience….Let’s see where this goes.

I get a ton of info from YouTube as I am a visual learner. Much to the disdain of my wife who endures endless videos playing in the background about things that make no sense to most humans.

So, like many, I came across Supabase on YouTube and even picked up some paid overview courses on it.

Traditionally I’ve been a backend developer and mostly focused in the AWS world building life and limb related services for emergency response systems. I can see the attraction if you’re mostly a front end developer and are just trying to get going quickly and haven’t taken the time yet to build a backend API. I will say, you should take some time to do that…it’s not that hard these days. 🙂

FlutterFlow has embraced Supabase with amazingly simple integrations which allow one to use Supabase as the authentication provider, as well as a code free means of integrating Supabase data into their UI builder. It’s honestly quiet easy to use. The speed at which you can get a project up and running is impressive. I totally see how one could get sucked into this.

So, Ok what’s this security issue I’m concerned about?

I was watching a recent video on Theo Brown’s @t3dotgg’s channel covering the recent Firebase hack “The $4 BILLION Hack That Everyone Missed”.

The TL;DR on this a couple security researchers had a hunch that new developers were launching apps with improperly secured firebase tables. Combined with some home grown tooling, they embarked on a web scraping campaign and found a ton of sites that were exposing customer data. They then reached out to notify the owners of the sites and documented their experience. The experience they shared was pretty alarming.

You can read about it all on How I pwned half of America’s fast food chains, simultaneously. and how we owned almost all of america’s fast food chains.

Positively eye opening.

Theo shared his opinion, in the video, that it wasn’t surprising to him, as outsourcing the backend to a third party was folly in his mind. He even made reference to Supabase and while they have done more to make the solution secure by default when creating tables, he said he still advocates to build your own backend and just use database integration to it vs. trusting the auto magically generated Supabase API.

So I consumed this video, and filed it away as something to poke around on Supabase to see if I could find something that could be easily overlooked, much like the Firebase incident that turned into a HUGE issue.

Well folks, I believe I’ve found one such example.

Like Firebase, there is something you can do that opens up your data to the world and it’s going to surprise a lot of you. It surprised me!

Now to be completely fair, this issue is totally documented in Supabase online documentation surrounding RLS (Row Level Security). This is a PostgreSQL “feature” which you can also read about in their PostgreSQL docs.

I honestly hadn’t played with the feature, so I learned of it when I started playing with Supabase. Most of my time has been with MySQL and later AWS Aurora MySQL variants.

Supabase realizes RLS is a new concept so they have done a great job producing countless videos as have others on the topic. Multiple YouTube creators in the no-code space have also done the same.

So what’s the problem?

A VERY common approach for app developers using Supabase is to also create views to solve getting the right information to a screen where a single query against 1 table won’t suffice. Tools like FlutterFlow will then happily, with no code, let you hook that view into your UI.

Not familiar with the syntax for the view? No Problem! Supabase AI to the rescue! It’ll happily create the SQL for you to create these views and you’re off the the races!

Except…..there’s a really surprising default behavior to this.

Views, it turns out, by default run as the user that created them and bypass the RLS policies of the tables upon which they use.

Said another way, if you create a view, by default the data is open to the world.

Anyone with the ANON public key (which can be obtained from your front end, in plain view to anyone) can get read only access to all of this data. All records of all tables within that view.

Right in the Supabase docs, it does say:

Supabase Doc Snippet: Views

Views bypass RLS by default because they are usually created with the postgres user. This is a feature of Postgres, which automatically creates views with security definer.

In Postgres 15 and above, you can make a view obey the RLS policies of the underlying tables when invoked by anon and authenticated roles by setting security_invoker = true.”

 create view <VIEW_NAME>
 with(security_invoker = true)
 as select <QUERY>

Not what you expected?

This behavior, I’m betting, is not the expected behavior that all of these no/low-code developers are expecting. Heck, it wasn’t for me either.

This is a feature, this isn’t a bug. Working as expected?

Supabase has taken better steps than most in this space in building a system that tries to help developers do the right thing, but I do worry (a lot more now) that they’ve built something that’s allowing small teams WITHOUT backend expertise to think they can just “outsource” that responsibility.

You can, but that doesn’t remove your responsibility to secure your customer data. Outsourcing this makes it too easy, in my opinion, to make incorrect assumptions.

Of course, the same can be said if you’re building the backend yourself and leveraging security frameworks. Just be mindful of what you’re getting and test and verify.

Build it in a weekend? Sure, but…maybe spend a little more time testing before you push your little bundle of joy to the world.

Don’t Do This

The below SQL if used will bypass RLS on tables profile and membership. Don’t do this!

 create view
  public.memberships_profile_view as
select
  m.id as membership_id,
  m."membershipID",
  m."membershipStartDate",
  m."membershipEndDate",
  m."membershipLevel",
  p.id as profile_id,
  p."firstName",
  p."lastName",
  p.email
from
  membership m
  join profile p on m.user_id = p.user_id;

Use security_invoker = true

In this next example below, the same view as above is created but includes the WITH clause to enable security_invoker to true. Using this will honor your RLS rules which is probably what you’d expect would be the default, but it’s not.

CREATE VIEW public.memberships_profile_view
WITH (security_invoker = true) AS
SELECT
  m.id as membership_id,
  m."membershipID",
  m."membershipStartDate",
  m."membershipEndDate",
  m."membershipLevel",
  p.id as profile_id,
  p."firstName",
  p."lastName",
  p.email
FROM
  membership m
  JOIN profile p ON m.user_id = p.user_id;

We’ve contacted a number of YouTube content and course creators that cover Supabase and FlutterFlow to update their content to include this precaution on views. I’m happy to say many have acknowledged and agreed to do so, but haven’t yet. I’ve reached out to Supabase as well asking them to update their content to cover this. In the interim, please make sure to spread the word on this. I’ve very concerned this is widely deployed incorrectly.

My important safety tip of the week. Trust but verify.

Jim Hankins

Cloud Bedrock

Leave a Reply

Your email address will not be published. Required fields are marked *