Guides / Firebase security

Firebase security rules: the lock your vibe-coded app forgot to turn.

Plenty of AI-built apps, including many built by South African founders, run on Firebase. The pattern of failure is the same as everywhere else: the demo works, the database is open, and nobody notices until someone hostile does. This guide shows you how to check your project and write rules that hold.

Your Firebase API key is not the secret. The rules are.

The Firebase config block in your frontend, apiKey included, identifies your project. It does not authorise anything. Like Supabase's anon key (now the publishable key), it is designed to ship in client code, and anyone can read it there. What decides whether a stranger can read your data is Firebase Security Rules: the per-request conditions attached to Cloud Firestore, the Realtime Database, and Cloud Storage.

When the rules are open, the public config is full access to your database. No exploit, no skill, just a URL. And AI coding tools set projects up fast, wire the happy path, and rarely circle back to the rules.

This failure mode is measured, not hypothetical In 2024, security researchers found more than 900 websites with misconfigured Firebase instances exposing about 125 million user records, including over 20 million plaintext passwords and 27 million billing entries. A separate GitGuardian analysis tied Firebase misconfigurations to nearly 20 million leaked secrets. The tooling changed since then; the mistake has not.

Test mode is a 30-day timer, and both endings are bad.

When you create a Firestore or Realtime Database in test mode, Firebase writes rules that allow anyone to read and write until an expiry date about 30 days out:

rules_version = '2';
service cloud.firestore {
  match /databases/{database}/documents {
    match /{document=**} {
      allow read, write: if request.time < timestamp.date(2026, 8, 3);
    }
  }
}

Ending one: the date passes, every request in your app starts failing with permission-denied, and a rushed founder "fixes" it by changing the condition to if true. The app works again, permanently open. Ending two: you are still inside the window right now, which means your database is open right now. Either way, the fix is the same: real rules.

Check whether your project is open, in five minutes.

  1. Probe the Realtime Database. If your project uses RTDB, this one-liner is the classic open-database check. Run it signed out, from any terminal:
    curl "https://YOUR-PROJECT-default-rtdb.firebaseio.com/.json"
    Data back means the whole tree is public. A permission-denied error is what you want to see.
  2. Probe Firestore. Swap in your project ID and a collection name your app uses, such as users or orders:
    curl "https://firestore.googleapis.com/v1/projects/YOUR-PROJECT/databases/(default)/documents/users"
    Documents in the response mean that collection is readable by anyone.
  3. Read your rules in the console. Firestore, Realtime Database, and Storage each have their own Rules tab. Search your rules and your repo for if true and for expired test-mode timestamps.
  4. Check Storage separately. Uploaded files are governed by their own rules. A locked Firestore with an open bucket still leaks every document your users ever uploaded.

If a probe returned personal information, treat it as a potential incident. Under POPIA, publicly reachable personal data can carry notification obligations; our POPIA guide for app founders covers what that means in practice.

Write rules that check who is asking.

Rules are conditions evaluated by Firebase on every request. The two building blocks are request.auth, who is asking, and request.resource, what they are trying to write. A sane baseline for user-owned data:

rules_version = '2';
service cloud.firestore {
  match /databases/{database}/documents {
    match /users/{userId} {
      allow read, update, delete: if request.auth != null
                                  && request.auth.uid == userId;
      allow create: if request.auth != null
                    && request.auth.uid == userId;
    }
    match /{document=**} {
      allow read, write: if false;
    }
  }
}

The final block is the important habit: default deny. Anything you did not explicitly allow is refused, so a collection added by your AI tool next week ships closed instead of open.

Two distinctions that catch vibe-coded apps: first, request.auth != null means "any signed-in person", not "the right person". Any stranger can create an account, so authentication alone is not authorisation; compare the UID or a role. Second, the Admin SDK bypasses rules entirely. That is correct for trusted server code, and catastrophic if your service account JSON ever reaches a browser or a public repo.

Validate writes too. Rules can enforce shape and bounds, for example request.resource.data.price is number && request.resource.data.price >= 0, which blocks a whole class of tampered-client tricks.

Where it goes wrong

The six Firebase mistakes we keep finding in AI-built apps.

Rules

if true in production

Usually the aftermath of an expired test mode. It reads like a placeholder and behaves like a public database.

Authz

Signed-in means allowed

Rules that only check request.auth != null let any stranger with a free account read every user's data.

Coverage

No default deny

Without a closing match-all deny, every collection your AI tool adds next sprint starts life exposed.

Storage

Open buckets

Firestore locked, Storage forgotten. Uploaded IDs, invoices, and photos leak just as badly as database rows.

Keys

Service account in the repo

The Admin SDK JSON bypasses all rules. In a public repo or client bundle it is game over; rotate it immediately.

Frontend

UI-only permission checks

Hiding the admin screen does not protect the collection behind it. If the rule is not enforced by Firebase, it is not enforced.

Test the rules, then keep them tested.

  • Use the Rules Playground in the console to simulate reads and writes as specific users before you publish a change.
  • Run the Firebase emulator suite locally and write a handful of unit tests against your rules; they are plain assertions and catch regressions cheaply.
  • Re-run the two curl probes above after every schema change, and once more after launch. They take seconds and fail loudly.
  • Turn on App Check when you are ready; it verifies requests come from your real app, which raises the bar for scripted abuse on top of correct rules.

Questions

Questions founders ask.

Is it safe that my Firebase apiKey is visible in the app?

Yes. The apiKey in your Firebase config identifies your project; it does not grant access on its own. Access is decided by Security Rules, plus App Check if you enable it. The key only looks like a secret. The rules are the real control, which is why an open ruleset is so dangerous.

What happens when Firebase test mode expires?

Requests from your app start failing with permission-denied errors, usually about 30 days after the database was created. The correct response is to write real rules for the collections your app uses. The common wrong response is changing the rule to allow read, write: if true, which silently turns your database public.

Do Firestore security rules apply to the Admin SDK?

No. The Admin SDK and service accounts bypass Security Rules entirely, which is intended: trusted server code needs full access. It also means your service account JSON is a master key. Keep it in server-side secret storage only, and rotate it immediately if it has ever been committed to a repo.

How do I test Firestore security rules?

Three layers: the Rules Playground in the console for quick simulations, the Firebase emulator suite with unit tests for regressions, and signed-out curl probes against your live project for ground truth. If you only do one, do the curl probes; they test what an attacker actually sees.

My Firebase data was publicly readable. What should I do?

Lock the rules first, then assess. Work out which collections were reachable and whether they held personal information. If they did, treat it as a potential incident under POPIA, including possible notification duties, and consider a broader review, because an open database is rarely the only gap in an AI-built app.

Beyond the rules

Rules are one gap. A production-readiness review covers all of them.

Secrets, sign-in, data integrity, reliability, and hosting you own: our senior engineers review the app you built with AI and give you a ranked list of what must be fixed before real users, what can wait, and what is already fine.