← Blog

The file-upload feature that lets anyone store anything on your dime

“Let users upload a profile picture” sounds trivial, and an AI will build it for you in one prompt. What it usually builds is an open door: a feature that accepts any file, of any size, from anyone, into a bucket the whole internet can read. Uploads are one of the most under-secured features in vibe-coded apps, and they go wrong in several directions at once.

What can go wrong

  • No type restriction. “Profile picture” that also accepts .html, .svg or .exe. An HTML or SVG file served from your domain can run scripts in your users’ browsers; other types turn your storage into a malware host.
  • No size limit. One user uploads a 5 GB file — or a script uploads ten thousand of them — and your storage bill and bandwidth scale with the abuse.
  • Public bucket. The storage is world-readable (or world-writable), so private uploads aren’t private and anyone can dump files in.
  • Predictable URLs. Files at /uploads/1, /uploads/2 can be enumerated — someone reads everyone’s uploads by counting.
  • No auth on the upload. Anyone, logged in or not, can store files on your account.

Why AI builds it loose

The prompt was “make upload work,” and the loosest possible version is the one that works fastest in the demo. Type checks, size caps, private buckets and signed URLs are all friction the happy path doesn’t need — so they’re not there.

How to lock it down

  1. Validate type and size on the server — an allow-list of real types (not just the file extension), and a hard size cap.
  2. Keep buckets private; serve via signed URLs with short expiry, so only authorized users get a working link.
  3. Scope every file to its owner and check ownership on read and delete — the same tenant-isolation rule as the rest of your data.
  4. Use random, unguessable names, not sequential ids.
  5. Serve user content from a separate domain so an uploaded HTML/SVG can’t run scripts against your main app’s session.

On Supabase Storage and Firebase Storage this is enforced by the same rules that protect your database — and they ship with the same open-by-default trap. A public bucket is the storage version of an open table.

How to check

Try to upload a .html or .svg, and a very large file — are they accepted? Open someone else’s uploaded file by guessing its URL — does it load? Upload while logged out — does it work? Any “yes” is a hole.


Upload handling touches storage cost, data privacy and even XSS in one feature, which is why it’s worth a careful read before launch. A seniorgrade audit checks exactly these paths; if you’re on Supabase, the Supabase breakdown covers storage rules specifically, and Bolt-built backends hit the public-bucket trap often.