← Back to blog
SecurityScanaris Team · 6 min read · October 04, 2026

The most common security mistakes in Firebase

Firebase is the favorite backend of many AI-built apps: Lovable, Bolt or Cursor wire it up in minutes and you get authentication, a database and file storage without running a server. The problem is that its security model is the opposite of a traditional backend's. Here the browser talks directly to the database, so there is no server filtering requests: the security rules are your only barrier, and one badly written rule leaves all the data in plain sight. This guide covers the ten mistakes that come up most often, with the wrong rule and the right one in each case, and ends with how to check it yourself.

1. Test mode rules, or what comes after them

When you create a database, Firebase offers "test mode": everything open for thirty days. The rule looks like this:

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

Until that date, anyone with your projectId (which is in the browser code) can read and write the entire database. And the worst comes afterwards: when it expires, the app stops working, the developer looks for a quick fix and replaces the date with if true. Now the hole is permanent. The fix is to write per-collection rules from day one, based on who the user is and whose document it is.

2. allow read: if true on user data

Some collections can be public (a product catalog, blog posts). But allow read: if true on users, orders or messages means anyone can download the full list. The correct way to protect per-user documents:

match /users/{userId} {
  allow read, write: if request.auth != null
                     && request.auth.uid == userId;
}

Each user reads and writes only their own document. For data shared among several (a team, a project), store the member list in the document and check it in the rule.

3. Confusing "authenticated" with "authorized"

The rule allow read: if request.auth != null looks safe and is the most common mistake. It says: any signed-in user can read. But signing up for your app is free and takes ten seconds, so in practice it is the same as open. Authenticated answers who you are; authorized answers what you may see. Rules have to answer the second.

4. Sensitive data in readable documents

A public profile with name and avatar is reasonable. The same document with email, phone, address and an access token is not. If other users must be able to see part of the profile, split public from private into two documents or a private subcollection with stricter rules. What is not in a readable document cannot leak.

5. Thinking the Firebase config is a secret

The object with apiKey, authDomain and projectId goes to the browser by design: it identifies your project, it does not authorize anything. Seeing it in the source is not a flaw, and it is not what you need to hide. What you should do with that key is restrict it in the Google Cloud console to your site's domains (HTTP referrer restriction) and enable App Check, which requires requests to come from your real app and not from a script.

6. Other keys are secret

The serious mistake is a different one: next to the Firebase config, the bundle carries the Stripe secret key, the OpenAI key or an email service key because "it was easier that way". Those keys give access to money and third-party accounts. Their place is a Cloud Function with the secrets in environment variables, and the browser calls the function, never the service directly. The detail is in secrets your app leaks in the browser.

7. Storage without its own rules

Cloud Storage has its own rules and they are often forgotten. With allow read, write: if request.auth != null any registered user can read other people's files, upload whatever they want to any path and fill your quota. A reasonable rule limits the path to the user and validates size and type:

match /users/{userId}/{fileName} {
  allow read: if request.auth != null && request.auth.uid == userId;
  allow write: if request.auth != null
               && request.auth.uid == userId
               && request.resource.size < 5 * 1024 * 1024
               && request.resource.contentType.matches('image/.*');
}

8. Open Realtime Database

If you use the Realtime Database instead of Firestore, the equivalent is ".read": true at the root. The rules are a different language, but the logic is the same: nothing open at the root, and per-branch permissions with auth.uid.

9. Writing without validation

Allowing the document owner to write is not enough if you do not validate what they write. A user can add isAdmin: true or plan: "pro" to their own profile from the browser console. Validate the fields with request.resource.data: which keys exist, what type they are and that sensitive fields do not change.

allow update: if request.auth.uid == userId
  && !request.resource.data.diff(resource.data).affectedKeys()
       .hasAny(['isAdmin', 'plan', 'credits']);

10. Forgetting the bill

Firebase charges per read. A rule that allows list without a limit on a large collection lets a script read millions of documents and hand you an unexpected bill. Limit queries from the rules (request.query.limit <= 100), enable App Check and set budget alerts in Google Cloud.

How to check it yourself

  • Rules simulator. In the Firebase console, Rules tab, there is a simulator where you test a read as an anonymous user or with a specific UID. Try reading users without a session: it must be denied.
  • Emulator with tests. The local emulator lets you write automated rule tests. It is the only way to keep them from breaking when you change them.
  • From outside, without a token. The Firestore REST API answers anyone:
curl "https://firestore.googleapis.com/v1/projects/YOUR_PROJECT/databases/(default)/documents/users"

If it returns documents, that collection is open. If it returns a permission error, the rule works. Repeat for every collection you have.

Scanaris reviews your Firebase from outside

Scanaris detects the Firebase config in your bundle, checks whether the usual collections answer without authentication, looks for other keys that should not be in the browser and reviews headers, CORS and exposed files. For every finding it gives you a fix prompt with the exact rule or change to paste into Lovable, Bolt or Cursor. These are read-only checks on what is already public. The full list of risks is in the Firebase security guide, and before launching it is worth going through the security checklist.

Frequently asked questions

Is it a problem that my Firebase apiKey is in the code?

Not by itself: it identifies the project, it grants no permissions. What protects the data are the rules. That said, restrict the key to your domains and enable App Check to prevent abuse.

What do I do if my rules are open right now?

Close first: set per-collection rules with request.auth.uid even if some feature breaks, and fix the app afterwards. Then check the usage logs for anomalous reads and, if personal data was exposed, consider whether you have to notify.

Can I ask my AI to write the rules?

Yes, and it usually does well if you give it the structure of your collections and who should see what. But test them in the simulator before publishing: an AI also writes if request.auth != null believing it is safe.

How does your site do?

Run Scanaris and check it in 30 seconds, free.

Scan my site

Related checks

Security guides for your stack