Back to Blog
engineeringauthstoragesecuritybugs

Why Supabase Storage Upload RLS Fails Silently (Simply Explained)

A plain-language guide to supabase storage upload rls fails. No jargon, no tech speak, just what it means for your business.

By Mike Hodgen

Want the full technical deep dive? Read the detailed version

A Feature That Worked in the Demo and Failed for Months

Let me tell you about a bug I shipped on my own app.

I run a DTC fashion brand out of San Diego, handmade goods. We built an internal app for our design team. One feature let designers upload files into it. Reference images, mockups, that kind of thing. Simple stuff.

We built it, demoed it, shipped it. In the demo a designer picked a file, clicked upload, the little "success" message cleared, everyone nodded. Done. Move on.

Months later I went looking for those uploaded files. I opened the digital folder where they were supposed to live, expecting a pile of images.

It was empty.

Not half-full. Empty. The feature had never worked once since the day we launched. Every upload a designer attempted over those months had failed quietly and gone nowhere.

The only sign anyone ever saw was a vague "upload failed" message. Most people ignored it. A few assumed they'd picked a bad file and gave up. Nobody flagged it, because nothing looked broken.

This is the worst kind of bug. When an app crashes, you find out right away and you fix it. When something fails quietly, it can sit there for months pretending to work while the truth is hiding in plain sight.

Why It Failed (In Plain English)

Think of our app like an office building with a security guard at the storage room door.

Most apps use the security system that comes built-in with their software. We didn't. We built our own login system because we wanted more control over how people signed in and what they were allowed to do. That's a normal, reasonable choice.

Here's the problem. The storage room had its own guard, and that guard only recognized the building's official ID badges. Our designers were carrying our custom badges instead. Real, valid badges. Just not the ones the storage guard was trained to recognize.

So when a designer tried to upload a file, the storage guard looked at them and saw a stranger. No recognized badge, no entry. The guard said no, every single time. And the guard was right to. You don't want strangers writing files into a private room.

The rejection came back as a generic error, and our app turned that into the only thing anyone ever saw: "upload failed."

The storage room was doing its job perfectly. It just couldn't see who our users really were, because their real identity lived in a system the guard couldn't read.

Why Nobody Caught It

The rest of the app felt totally healthy, and that's the trap.

Designers opened the app, saw their data, clicked around, everything responded. Reading information worked fine. The only broken part was that one privileged action: saving a file into the locked storage room. Everything else hummed along.

This is the thing about quiet failures. From the outside, a working feature and a broken one look identical until you actually go check the real data. The screen cleared the message. The app stayed responsive. Nothing in the experience told anyone the upload had died.

And here's the uncomfortable part for anyone who trusts a quick demo. A clean demo tells you almost nothing about whether something works for real. Our demo proved the button was clickable. It proved nothing about whether the file actually arrived.

That gap, between "the screen behaved" and "the data is actually there," is where features go to die quietly.

The Fix

The answer was to stop letting the browser talk to the storage room directly.

Instead, I built a trusted middleman on our own server. Think of it as a manager who sits between the designer and the storage room. The browser hands the file to the manager. The manager checks the designer's badge, confirms they're allowed to upload, and only then walks the file into storage personally.

The manager has a master key, so the storage guard always lets the manager in. But that master key never leaves the back office. It never touches anyone's browser. If it did, it would be like handing the master key to every stranger on the street.

The important part: because the manager has a master key, the manager has to do the security check the storage guard used to do. So before the manager touches anything, it verifies the designer's badge and confirms their role. Only designers can upload. Anyone else gets turned away on the spot.

I didn't skip security. I just moved it from the storage room (which couldn't read our badges) to the manager (who could).

Once I made that change, the very next upload landed on the first try. After months of an empty folder, files started arriving instantly. I never touched the storage room's rules. Those were right all along. The browser was simply the wrong place to be making the request.

One more thing worth saying: you don't fix this by routing every single action through a middleman. That would slow the whole app down for no reason. Reading public information can stay fast and direct. Only the locked actions, the ones where the storage room needs to know exactly who you are, need the manager. I add that step surgically, only where the mismatch actually breaks something.

Demos Lie. The Data Tells the Truth.

Step back, because the real lesson is bigger than one folder.

Apps built fast, under deadline pressure, are full of features that look finished and quietly fail the moment real logins and real permissions come into play. The demo runs on the easy path that never stresses the parts that break. Then real use hits, and the gaps go unnoticed because nothing crashes.

The only way to catch this is to go check the actual stored data. Not the screen. Not the success message. The data itself. Open the folder. Count the files. Confirm the thing is actually where it's supposed to be.

I now check this on every feature I ship. If a feature is supposed to save something, I go look at what actually got saved. It takes minutes and it catches the bugs that would otherwise sit silent for months.

Honest admission: I shipped this bug too. My team built it, I approved it, and it sat broken for months on my own brand's app. I'm not above this. The difference isn't that I never ship bugs. It's that I go find them, because I know exactly where to look.

If your team built an app fast, and you've never personally confirmed that the things it claims to save actually get saved, that's worth an afternoon of someone's time. The features that bite you are the ones that never throw an error. They demo clean, they ship, and they quietly do nothing for months while everyone assumes they work.

This is exactly the kind of gap I find when I audit a build. It's almost never the loud, obvious break. It's the upload that's been failing since launch, the report reading zeros, the save that goes nowhere.

I run real systems on this every day, across my own brand and client builds. I know where the bodies are buried because I've buried a few myself and dug them back up.

Ready to bring AI leadership into your company?

I work with a small number of companies at a time. If you're serious about AI, apply to work together and I'll review your application personally.

Apply to Work Together

Get AI insights for business leaders

Practical AI strategy from someone who built the systems — not just studied them. No spam, no fluff.

Ready to automate your growth?

Book a free 30-minute strategy call with Hodgen.AI.

Book a Strategy Call