00:00:00A report came out this month scanned about 20,000 indie apps and found that one in nine
00:00:04were exposing Supa-based credentials in front-end code.
00:00:08This is not in a server log or a private repo.
00:00:11This is just in the JavaScript every visitor downloads.
00:00:14And the thing is, these apps weren't hacked, they just shipped the secret by accident.
00:00:18To be clear, some keys are meant to be public.
00:00:21The problem is the pipeline, because the same mistake can ship a key that should never be
00:00:25in the browser in the first place.
00:00:27We have videos coming out all the time.
00:00:28Be sure to subscribe.
00:00:35Here's the simple truth that ties some people up.
00:00:38We all kind of know it, but it's easy to miss when you're moving fast.
00:00:41When you mark an environment variable as public, your build tool treats it like it belongs in
00:00:46the browser and it gets added to the client bundle.
00:00:50Next.js does this with Next.public, Vite does this with Vite, and SvelteKit does this using
00:00:56public.
00:00:57Next is not a safety label of any kind, that's literally the shipping label.
00:01:01Now we know that, but here's the Supa-based part.
00:01:04Some keys are meant to be public, like anon or publishable key, and some are private, like
00:01:10service role or secret keys.
00:01:12If a private key ends up in the browser, well, I think you can guess what happens.
00:01:17Role level security won't save you in that case.
00:01:20Supa-based has clear docs that say service keys can bypass RLS.
00:01:24So if that key is in your front end, all your policy work doesn't really matter anymore.
00:01:29Now let me show you exactly how this happened using my own sandbox.
00:01:33First, I built out a simple CRUD app using Next.js and I linked my Supa-based into it.
00:01:38Here's my EMV file, and here's the mistake.
00:01:41I put a key behind a public prefixed variable.
00:01:45This key is publishable, so it's expected to be visible.
00:01:48The real problem is that the exact same Next.public path can accidentally ship a private key too.
00:01:53When I ran npm run build to build it out, then I started my app.
00:01:59Here I am in Chrome.
00:02:00Let me just add some real quick info to our database, our CRUD app.
00:02:05Okay, now I can open the compile JavaScript bundle and I'm going to search for it.
00:02:10There it is.
00:02:12The URL and the key are literally sitting inside the file your users just pulled into their
00:02:18browser.
00:02:19And notice what did not happen.
00:02:21Nobody broke into this.
00:02:22I found this, right?
00:02:24Nobody exploited anything.
00:02:25This is just reading what the app already shipped to the public internet.
00:02:29If you can see it, anyone can see it.
00:02:32Open dev tools, look at the JavaScript files and search for it.
00:02:35That's all you got to do.
00:02:36So if your plan is nobody's going to look, well, the internet is full of people and bots
00:02:41whose job is literally to look for these kinds of things.
00:02:44So here's the fix that actually works.
00:02:47The browser should only call your API.
00:02:50Your API runs server side.
00:02:52That's where private keys live.
00:02:54Move the private operation into an API route or server function.
00:02:58The client calls your endpoint, your endpoint calls super base, then rebuild and recheck
00:03:03in the bundle.
00:03:04If the key is gone from the bundle, you actually fixed it, but don't just stop there and call
00:03:09it quits because there's other things you could do.
00:03:11Make sure row level security is enabled for user facing tables and make sure your policies
00:03:16do what you think they do.
00:03:18Spend some time testing too.
00:03:19I think this gets brushed over some.
00:03:21Now the part that keeps this from coming back, most people fix this once, then reintroduce
00:03:26it later during a rush.
00:03:28So add some guard rails, start with a secret scanning NCI so builds fail if a key shows
00:03:34up where it shouldn't.
00:03:36Then a PR role that anything with next public or Vite is treated as public by default because
00:03:41it is.
00:03:42Finally, add some rotation.
00:03:43If you even have a slight hint that keys were exposed, just rotate them.
00:03:47That's better than seeing how it plays out over a few days.
00:03:50Here's what you can try right now.
00:03:52Build your app the way you ship it.
00:03:55Search the output for super base JWT service secret and anything that looks like a token.
00:04:01If you find anything private, assume it's compromised because you found it.
00:04:05Rotate it and then change up your logic server side.
00:04:08If you remember one line from this video, make it this.
00:04:11If it's in the bundle, it's public.
00:04:13We'll see you in another video.