$100K WHAT ONE OPEN TABLE COSTS

5 security mistakes that cost $100,000

Every one of them ships an app that works perfectly. That is exactly why they survive to production.

  • All five are visible from your public URL.
  • None of them break your app, so nothing warns you.
  • Four take under a minute to check.
  • The fifth is the one everybody skips.
6 min read Supabase · Firebase · Lovable · Bolt · v0

An AI code generator optimises for one thing: an app that runs. It will happily write a database query that works, and the fastest working query is one with no access rules in front of it.

Nothing warns you. The app looks right, the data loads, the demo goes well. The five faults below are what that trade-off leaves behind.

$90,000+ Our estimated exposure for a site handling $50,000–$250,000 in annual business value. Below $10,000 in revenue it is nearer $3,000. Above $1m, north of $1.2 million. These are possibilities, not guarantees.
Highest impact of the five

Row-level security is off

Your Supabase publishable key ships in the app's JavaScript. That is normal. What decides whether it can do anything is row-level security.

With it off, that key reads every row of the table — not just the current user's. Emails, orders, messages, whatever is in there.

An app with no access policies works better than one with them. Nothing is being filtered.

That is why it survives. No error, no warning, no slow page.

Check it

Table Editor in your Supabase dashboard — tables without RLS carry a warning. Enabling it is step one; you also need a policy, or the table denies everything. On user data you want auth.uid() = user_id.

Watch the opposite failure too: USING (true) has RLS on and allows everyone. It looks configured. It is not.

Treat as already compromised

The service-role key is in the browser

Supabase gives you two keys that look nearly identical — both long, both starting eyJ. One is a public identifier. The other, service_role, bypasses row-level security entirely and can read, change or delete any row in any table.

It belongs on your server. When a generator needs a query to work regardless of policies, this is the key that makes it work.

Check it

View source, search eyJ, copy the string, base64-decode the middle section. If role reads service_role, rotate it now — it has been public for as long as the site has.

Bills you directly

A live payment or AI key shipped to the client

A Stripe sk_live_ key can create charges, issue refunds and read customer payment records. An OpenAI key bills your account on every call. An AWS AKIA key is a direct path to your storage and your bill.

These land in the bundle for the same reason as the service-role key: shortest path to a working feature.

Check it

Search your bundle for sk_live, AKIA, sk-proj. Any hit is public. Rotate first, then move the call behind your own server.

Hands over your source

Source maps left on in production

Source maps let you debug a minified bundle. Published, they let anyone rebuild your original code — comments, internal names, logic you assumed minification hid.

Not a breach on its own. A map for someone looking for one, and it usually reveals the endpoints and table names that make the other four easy.

Check it

Search your bundle for sourceMappingURL. Most build tools kill map output for production with one flag.

Quiet, but compounding

No security headers at all

Missing headers will not leak your database by themselves, which is why they get deprioritised forever. What they do is remove the guardrails that limit damage when something else goes wrong.

No Content-Security-Policy and an injected script runs unchallenged. No X-Frame-Options and your app can be framed and clickjacked. No Strict-Transport-Security and a first visit can be downgraded to plain HTTP.

Check it

Devtools, Network tab, click the first document request, read the response headers. If those three are missing, they are missing for everyone.

What it looks like when it goes wrong

Not hypothetical. In February 2026, researchers at Wiz found a Supabase database belonging to Moltbook — a viral social network for AI agents — wide open: 1.5 million API authentication tokens, 35,000 user email addresses, and 4,060 private conversations, readable and writable by anyone.

Nothing exotic was required. The database answered anonymous requests, and someone asked.

The 60-second version

  • View source on your live app.
  • Search for service_role, sk_live, AKIA, sourceMappingURL.
  • Rotate anything you find, then strip it from client code.
  • Confirm every table with user data has RLS on and a real policy.
  • Check your headers for CSP, HSTS, X-Frame-Options.

Step four is the one people skip, and the one that matters most.

Frequently asked

My app has been live for months with no problem. Am I fine?

Time live is not evidence. An open database produces no symptoms — no errors, no slowdown, no notification. The absence of a problem you could not see is not reassuring.

Is the anon key itself the vulnerability?

No. It is public by design and hiding it would break your app. The missing policy behind it is the vulnerability.

Can a scan confirm all five?

Four, from the public bundle and headers. Whether your tables are actually readable needs a query against your database, which needs your permission — so we report the exposure and say the policy was not tested. More on that here.