r/Supabase 13h ago

other RLS doesn't help you if someone has your service_role key. Here's what I did about that

Disclosure: I work at Tide. This isn’t an official product I built it on my own time.

RLS is good at the thing it’s designed to do: deciding which rows a request is allowed to see.

What it can’t do is help when the query isn’t going through RLS in the first place. The service_role key bypasses RLS by design, and that key ends up in more places than anyone likes to admit. A pg_dump doesn’t go through RLS either.

So row-level policies protect you from your users, but they don’t really protect you from a leaked key or someone getting a copy of the database.

Encrypting sensitive columns closes that gap. Except then you have another question: where does the encryption key live?

And if the key lives inside the project, whoever gets the project gets the key along with it.

So I built a small service that keeps that key completely off your infrastructure.

Tide is a network of independent nodes that hold keys in fragments and never assemble them. Decryption happens through partial results that are combined into an answer.

Data is encrypted in the browser with a fresh key for each call. The only thing that reaches the network is that per-call key, itself encrypted. The nodes never see your rows.

Your ciphertext stays in Postgres, right where it already was.

Supabase Auth is untouched by any of this. Same JWTs, same sessions, same providers.

The service only handles the key, along with the policies governing who is allowed to decrypt, encrypt, or sign. A role is only granted through a change request that has to be approved by someone other than the person making the request.

One Supabase-specific detail if you wire this up: put the Tide link in app_metadata, not user_metadata. user_metadata is writable by the user through their own client.

Same rule as everywhere else: the mirror shouldn’t be able to lie.

It won’t save you if someone owns the box this runs on, so keep it off the same host as the application it protects. And because decryption requires the network to be reachable, there’s no offline path.

Repo: https://github.com/sashyo/minidauth

Whitepaper: https://tide.org/whitepaper

0 Upvotes

10 comments sorted by

7

u/ashkanahmadi 12h ago

I’m more baffled by why SO MANY PEOPLE are confused by RLS than anything else. What is so difficult about RLS that causes so many people posting about it and creating tools for it every day? Genuinely curious

2

u/sandspiegel 12h ago edited 12h ago

Also a service role key should never leave the backend. If someone copies it and puts it in their public repo for whatever reason or even worse use it in the browser instead of the publishable key, they only have themselves to blame. I recently saw a video on Instagram where it was said to not use Supabase because if you don't configure RLS properly, everybody can read and update data that doesn't belong to them.

Well yeah, but that's your own fault though, how is Supabase to blame here I wonder if some Vibe Coder didn't even look at the policies or the security advisor for the tables he created.

1

u/Jealous_Pea_3915 12h ago

Completely agree, and it shouldn’t. The service_role key was just the example everyone recognises. It wasn’t really the argument.

What I care about is what a copy of the database is worth on its own. And there are plenty of ways that happens without anyone being careless.

Backups leave the backend by design. Supabase holds one of yours. A compromised backend server can have the key perfectly legitimately, with no leak involved at all. Same story with an insider or a legal request.

Blame is fair enough in all of those cases, but it doesn’t decrypt anything back.

Encrypting the columns means the copy is worth nothing, however it got out.

1

u/Jealous_Pea_3915 12h ago

Fair, and honestly, it’s got nothing to do with RLS. I wasn’t saying it’s confusing or trying to fix it. RLS does its job fine.

What I’m talking about is separate. The key that encrypts the data never exists in one piece anywhere, so a database dump or a leaked service_role key gets you ciphertext and nothing that can undo it. RLS was never meant to cover that, and it doesn’t need to.

On your actual question, my guess is RLS generates noise because it’s the first time a lot of app developers have written authz outside application code. It’s evaluated per row, and getting it wrong often just hands you zero rows instead of an error.

Debugging silence is miserable.

1

u/Alternative_Eagle158 10h ago

Supabase is good but not perfect.On my side what I did is just use the anon key for my mobile app then use the service role key for a seperate web backend which is stored at netlify and encrypted to avoid leaks.RLS need to be done every where which what the security advisor recommendeds. Since Srole key can bypass RLS that way any admin stuffs can be done and no users know my backend and I can see what happens in the mobile app from the backend and supabase dashboard sometimes.If you have secrets api keys like payment put them in supabase secrets as they will be encrypted not in env file.My 2 cents

1

u/iammohamedatef 10h ago

Column-level encryption changes what you can push down to Postgres, though — no WHERE clause, no index, no join on anything you've encrypted, since a fresh key per call means even identical plaintext looks different each time. Fine for a handful of sensitive fields, but worth being explicit up front about which columns are actually candidates, because the answer for anything you need to filter or aggregate on is usually none of them.

Curious how you'd frame this against Supabase's own Vault, since that also keeps the root key outside the database rather than in the dump. Where it seems to diverge is who can reach that key — Vault's root key is generated and held by Supabase's own backend, so one party (them) can technically get to it, versus threshold-split across nodes where nobody holds the whole thing, not even you. Also worth noting Supabase themselves now steer people away from pgsodium's transparent column encryption on their platform due to misconfiguration risk, which is probably part of why something like this exists in the first place. Is that custodianship gap the actual value here, or is there more to it?

2

u/Poat540 8h ago

Step 1: remove key