ShipSafe

Firebase · 10 min · Updated 28 Aug 2026

Is my Firebase app secure? The rules mistakes that expose everything

Your Firebase config is meant to be public. Security rules are the only control left — here is how to tell whether yours hold.

The first thing to settle, because it is the question everyone asks first: no, finding your Firebase config in your JavaScript is not a leak. The apiKey, projectId and authDomain in that object are identifiers, not credentials, and Google publishes them in its own documentation. They are supposed to be in your bundle.

What follows from that is the part worth your attention. Because the config is public, anyone can point the Firebase client SDK at your project and start making requests. Your database is on the internet, addressable by strangers, by design. The only thing deciding what those requests return is your security rules.

The one thing to know

Firestore, Realtime Database and Storage each have their own rules, and they are independent. Locking down Firestore does nothing for Storage. Most exposures we see are not a project with no rules — they are a project where one of the three was tightened and the other two were never revisited.

§ 01

Read your rules before anything else

Open the Firebase console, then Firestore Database, then Rules. Do the same for Realtime Database and for Storage. You are looking for one pattern, in any of the three.

rules_version = '2';
service cloud.firestore {
  match /databases/{database}/documents {
    match /{document=**} {
      allow read, write: if request.time < timestamp.date(2026, 9, 15);
    }
  }
}
Test mode. Anyone with your public config can read and write everything until the date passes — and then your app breaks instead of getting safe.

This is what "Start in test mode" writes, and what a coding agent will happily leave in place because the app works perfectly with it. It is a global allow with an expiry date. Both halves are bad: before the date, every document in your project is world readable and world writable; after it, every request fails and your product goes down.

match /{document=**} {
  allow read, write: if request.auth != null;
}
The other pattern to look for: authenticated, but not scoped. Any signed-up user reads every document.

This one is more insidious, because it looks like a security control and passes a casual review. It checks that the caller is signed in. It does not check that the document belongs to them. Anyone can sign up for your app in ten seconds, so the practical difference between this and no rules at all is ten seconds.

§ 02

The rules mistakes coding agents make

Four patterns account for nearly everything we find in AI-built Firebase projects. Each one produces an app that works flawlessly for the person testing it.

Ownership checked on the way in, not on the way out. A create rule verifies request.resource.data.userId == request.auth.uid, so users can only create their own documents — and the read rule never checks resource.data.userId, so they can read anyone's.

A recursive wildcard hiding under a specific rule. A carefully scoped rule on /users/{uid} sits above a leftover match /{document=**}. Rules do not override each other — access is granted if any rule allows it. The broad one wins.

Storage left on the default. Firestore gets the attention because that is where the agent wrote the data model. Uploaded files — avatars, invoices, ID documents — sit in Storage under rules nobody read.

Validation only in the UI. The app sets the role field to user and the rules permit writing any field. A client that writes role: "admin" to its own document is not hacking anything; it is using the API as written.

Why one account cannot find these

Every pattern above behaves correctly when the only person testing is the owner of the data. You need a second account, and a document ID that belongs to the first, to see the difference. That is why an external check of a Firebase app can never honestly return "Ready to ship" on authorization — it has not logged in.

§ 03

The 10-point Firebase checklist

Run this before real customers arrive. Items 01 to 06 you can check from the console in a few minutes; 07 to 10 need two accounts.

  1. 01No rule in any of the three services allows read or write based only on a date, and no request.time expiry remains anywhere.
  2. 02No rule grants access on request.auth != null alone for user-owned data.
  3. 03Read rules compare resource.data ownership, not just request.resource.data on create.
  4. 04There is no leftover match /{document=**} allow rule beneath your specific rules.
  5. 05Storage rules are scoped per user path, and files holding customer data are not readable while signed out.
  6. 06Realtime Database rules were reviewed too, even if you think you are not using it — a project can have both.
  7. 07Write rules restrict which fields can be set, so a client cannot promote itself by writing a role or plan field.
  8. 08Account A, signed in, cannot read account B's document by its ID through the SDK or the REST API.
  9. 09Account A cannot update or delete account B's document. Read scoping and write scoping are separate rules.
  10. 10Any Cloud Function that reads protected data checks the caller itself. The Admin SDK bypasses security rules completely.
The last one is easy to miss

Cloud Functions run with the Admin SDK, which is not subject to security rules at all. A callable function that fetches a document by an ID the client supplied, without checking who is calling, is a hole straight through every rule you just tightened.

§ 04

How to check it yourself, concretely

Two accounts, five minutes. Sign in as account A in your app and create a record. Copy its document ID from the console. Sign in as account B — a second real signup, in a different browser profile — and try to fetch account A's document by that ID.

GET https://firestore.googleapis.com/v1/projects/<project-id>/databases/(default)/documents/invoices/<A-document-id>
Authorization: Bearer <account B's ID token>

# a correctly scoped project answers
HTTP/1.1 403 Forbidden   { "error": { "status": "PERMISSION_DENIED" } }

# an exposed project answers with account A's data
HTTP/1.1 200 OK          { "fields": { "customer": ... } }
Firestore also speaks REST, which makes this testable without writing app code. Substitute your project ID and account B's ID token.

Do the same for update and delete, and repeat both against a Storage object. Four requests, and you know something you previously only believed.

§ 05

After you tighten the rules, prove it

Firebase rules are unusually easy to fix wrongly, in either direction: too loose and nothing changed, too tight and your own app stops loading. So verify both sides. Send account B's request again and confirm it is now refused. Then sign in as account A and confirm your own data still loads.

A finding is not closed because you edited a rule. It is closed when the request that reached another customer's data can no longer reach it — and the only way to know that is to send it again.

Questions people ask

Short answers, no hedging

Is it a problem that my Firebase API key is public?
No, on its own. The Firebase web config, including apiKey, is meant to ship in your app and identifies your project rather than authorizing access. It becomes a problem only because it tells anyone which project to talk to, which means your Firestore and Storage rules are the only control standing between a stranger and your data.
What is the most common Firestore rules mistake?
A rule that checks only that the caller is signed in. Any visitor can create an account, so allow read: if request.auth != null grants every account access to every document. Ownership has to be compared against the document, for example against a uid field on the record or the document path itself.
Do Storage rules need to be set separately from Firestore rules?
Yes. Firestore and Cloud Storage have independent rule sets, and locking one down does nothing for the other. Uploaded files are the ones most often left open, which is how a customer's uploaded document ends up on a URL that needs no login at all.
How do I test my Firebase rules?
Sign in as one account and request a document that belongs to another by its identifier, using the API directly rather than your UI. The UI is not the boundary and never was. Correct rules refuse the request; if the document comes back, so would it for anyone who read your bundle.
Run the check on your own app

Stop reading about it. Point us at your URL.

The free Preflight is passive and read-only. It checks your app for exactly the exposures this guide describes and returns a score, a verdict and one finding with its evidence. No signup, no card.

Keep reading