Home › Blog › Firebase Security Rules

Firebase Security Rules: 7 mistakes that leak your whole database

October 12, 2026 · ~7 min read

Firebase Security Rules are the only thing standing between your database and the internet. When you ship a SaaS fast — especially with AI help — that is exactly the layer that tends to get left for later. Below are the 7 mistakes we see most in audits, each with the wrong code, the right code, and why it matters.

Why this is serious: unlike a back-end bug, an open rule throws no error — it works beautifully. The leak only shows up when someone opens DevTools, copies your Firebase config (public by design) and queries your database directly, bypassing your app entirely.

1. The test-mode rule that shipped to production

Every Firebase project starts with a temporary rule. It is mistake number one because it is literally the default, and nobody remembers to change it back.

// ❌ Anyone on the internet reads and writes everything
match /{document=**} {
  allow read, write: if true;
}

A subtler variant uses an expiry date that has already passed (request.time < timestamp.date(2026, 1, 1)) — after the deadline the rule denies everything, so someone "fixes" it by swapping in if true. The fix is to never leave if true on a collection with real data: every read and write needs an explicit condition.

2. Confusing "is logged in" with "is the owner"

This is the classic multi-tenant SaaS leak. The rule only checks whether an authenticated user exists — so any logged-in customer can read every other customer's data.

// ❌ Any logged-in user reads any order
match /orders/{id} {
  allow read: if request.auth != null;
}

// ✅ Only the document owner reads
match /orders/{id} {
  allow read: if request.auth != null
    && resource.data.ownerId == request.auth.uid;
}

In OWASP terms this is an IDOR/BOLA (Broken Object Level Authorization) — the most common authorization flaw in APIs. Every collection with per-customer data must tie the document to request.auth.uid (or the user's tenant/clinicId). It's the subject of our guide on multi-tenant isolation.

3. Trusting a field the client controls

You can write a rule that looks safe but reads the user's role from a place the user can change:

// ❌ "role" comes from the document the user writes
allow write: if request.resource.data.role == 'admin';

// ✅ "admin" comes from the auth token (custom claims), not client-editable
allow write: if request.auth.token.admin == true;

Permissions must come from a source the client cannot forge: custom claims on the token, or a document only the back-end writes. Never from a field submitted in the very write the rule is meant to protect.

4. Not validating what is being written

Rules are not just "allow or deny" — they are your data-integrity validator at the database layer. Without validation, a client can write a negative balance, an "enterprise" plan they never paid for, or inflate a usage counter.

// ✅ Validate shape, size and immutable fields
match /profiles/{uid} {
  allow update: if request.auth.uid == uid
    && request.resource.data.plan == resource.data.plan   // can't change own plan
    && request.resource.data.email is string
    && request.resource.data.name.size() < 120;
}

5. The recursive wildcard that opens everything below

{document=**} matches the collection and every subcollection. A permissive rule at the wrong level leaks everything nested inside it.

// ❌ Also exposes the /users/{uid}/private subcollection
match /users/{uid}/{document=**} {
  allow read: if request.auth != null;
}

Remember: rules do not cascade top-down (a rule on /users does not protect /users/x/cards), but =** does cascade downward. Be explicit at each level.

6. Securing Firestore and forgetting Storage

Firestore, Realtime Database and Cloud Storage have separate rule sets. It is common to lock down Firestore and leave Storage on the open default — and users' uploads (documents, receipts, photos) become public to anyone with the URL.

// storage.rules — ✅ each user only touches their own folder
match /uploads/{uid}/{file} {
  allow read, write: if request.auth != null
    && request.auth.uid == uid;
}

7. Assuming Rules replace the back-end

Security Rules protect database access, but they do not rate-limit, do not verify webhook signatures, do not stop a bot, and do not validate complex business logic. For the rest you still need App Check (proves the call comes from your app), Cloud Functions for sensitive operations, and server-side validation. Rules are one layer, not the whole wall.

How to audit your rules today: run the Firebase emulator with tests that try to read another user's document; use the Rules Playground in the console to simulate an unauthenticated get; and grep your rules files for any if true, a bare request.auth != null (with no ownership check), and {document=**}.

Don't want to hunt for these by hand?

SAAS SHIELD runs a security review of your React/Next.js repository on Firebase or Supabase and returns every flaw with its location, evidence and fix.

Request an audit →