08 November 2026

Firebase Security Rules: 7 Mistakes That Expose Your Data

By the Ark Crew team · 5 min read

Flame with a padlock, illustrating Firebase Security Rules

Firebase apps, including many built with FlutterFlow, Firebase Studio and other AI-assisted tools, read and write Firestore and Cloud Storage directly from the browser or the phone. The web config with its apiKey is public by design: it only tells the client which project to talk to. What decides who can read or change each document and file is a short file you may never have looked at: your Security Rules. Firebase checks them on its servers for every single request.

When rules are wrong, the app usually works perfectly for its owner, and that is exactly why the mistakes survive until launch. Here are the seven we see most often, how to recognise each one, and the rules that fix it.

A quick check first

In the Firebase console, open Firestore Database → Rules, then Storage → Rules (and Realtime Database, if you use it). Look for four things:

  • if true anywhere
  • a date such as request.time < timestamp.date(...)
  • request.auth != null used on its own
  • match /{document=**} at the top level

Firebase also emails project owners when it detects rules that leave a database open. If you have been ignoring those emails, now is the time to read them.

1. Test mode rules that were never replaced

When you create a database in test mode, Firebase writes rules like these:

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

Until that date, anyone on the internet can read, change and delete every document. After that date, everything is blocked and the app stops working. The common "fix" is to move the date forward, which reopens everything. The real fix is rules written for each collection, as in the examples below.

2. Rules that allow everything

allow read, write: if true; in Firestore, or ".read": true, ".write": true in the Realtime Database, publishes your whole database. Data that really is public, such as a product catalogue, can have allow read: if true;, but give it its own match block and never allow writes that way.

3. Treating "signed in" as "allowed"

This looks responsible, but it is not:

match /{document=**} {
  allow read, write: if request.auth != null;
}

Anyone can create an account in your app, and if anonymous sign-in is enabled they do not even need an email address. Every account can then read and overwrite every other user's data. Check who owns the data instead:

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

match /orders/{orderId} {
  allow read: if request.auth != null && resource.data.userId == request.auth.uid;
  allow create: if request.auth != null && request.resource.data.userId == request.auth.uid;
}

In a rule, resource.data is the document as it is stored now, and request.resource.data is the document as it would be after the write. Checking the second one on create stops users from creating orders in someone else's name.

4. Wildcards that open more than you meant

match /{document=**} covers every document in every collection, including subcollections you add next year. And Firestore rules only ever add access: if any rule allows a request, it is allowed, so a broad rule at the top silently overrides the careful rules below it. You cannot write a narrower rule that takes access away again.

Delete catch-all rules and match each collection explicitly. A collection without a rule is closed, which is the safe default.

5. Letting users write any field

A rule that lets users update their own profile lets them update every field in it: role, plan, credits, isVerified. Name the fields a user may change:

match /users/{userId} {
  allow update: if request.auth != null
    && request.auth.uid == userId
    && request.resource.data.diff(resource.data).affectedKeys().hasOnly(['displayName', 'photoURL'])
    && request.resource.data.displayName is string
    && request.resource.data.displayName.size() <= 100;
}

On create, limit the fields with request.resource.data.keys().hasOnly([...]) and check their types and sizes the same way. Rules are also your input validation, since the client can send anything.

6. Admin roles that users can edit

Many apps check for an admin like this: get(/databases/$(database)/documents/users/$(request.auth.uid)).data.role == 'admin'. That is only safe if users cannot change their own role field (see mistake 5), and often they can. The more robust option is a custom claim set by your server with the Admin SDK, for example admin: true, and checked in rules as request.auth.token.admin == true. Users cannot set custom claims themselves.

7. Storage that is more open than you think

Cloud Storage has its own rules file, and test-mode Storage rules are just as open as test-mode Firestore rules. There is one more catch: download URLs created with getDownloadURL() contain a token and work for anyone who has the link, whatever your rules say. Treat them as public links, and do not use them for private files such as invoices or ID scans.

Rules that keep each user's files private, with type and size limits on uploads:

rules_version = '2';
service firebase.storage {
  match /b/{bucket}/o {
    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/.*');
    }
  }
}

The API key: restrict it, but do not panic about it

Finding your Firebase apiKey in the JavaScript is not a breach: it is meant to be there. Still, restrict it in the Google Cloud console to your own domains and apps and to the APIs you actually use, and consider App Check, which makes it harder for scripts outside your app to use your backend. Neither replaces good rules. Real secrets, such as a service account key or the Admin SDK credentials, must never be in client code. See our guide to exposed API keys for how to check.

"Missing or insufficient permissions" after tightening the rules

Firestore rules are not filters. A query must only ask for documents the user is allowed to read, otherwise the whole query is refused. If your orders screen breaks after you fix the rules, do not loosen them again: add the filter to the query, for example where("userId", "==", user.uid), so it matches what the rules allow.

Test your rules like an attacker

The Rules Playground in the console lets you simulate a request as a signed-in or signed-out user. Better still, write tests with the Firebase Emulator Suite and the @firebase/rules-unit-testing package, so a later change cannot quietly reopen your data:

import { initializeTestEnvironment, assertFails, assertSucceeds } from "@firebase/rules-unit-testing";
import { doc, getDoc } from "firebase/firestore";
import { readFileSync } from "fs";

const env = await initializeTestEnvironment({
  projectId: "rules-test",
  firestore: { rules: readFileSync("firestore.rules", "utf8") },
});

const alice = env.authenticatedContext("alice").firestore();
await assertSucceeds(getDoc(doc(alice, "users/alice")));
await assertFails(getDoc(doc(alice, "users/bob")));
await assertFails(getDoc(doc(env.unauthenticatedContext().firestore(), "users/alice")));

Need a hand?

Security Rules are one of the first things we check in our AI app security audit, together with exposed keys, access between users, admin routes and payments. You get a plain-English report with proof and fixes, from tests run only with your written permission and our own test accounts. If your app runs on Supabase instead, read our Supabase RLS guide, and for everything else before launch, see the vibe coding security checklist.

Frequently asked questions

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

Yes. The Firebase web config, including its apiKey, is meant to be public: it only identifies your project. Your data is protected by Security Rules. Restrict the key to your own domains and APIs, and never put service account keys or Admin SDK credentials in client code.

Are Firestore rules with request.auth != null secure?

Not on their own. Anyone can create an account in most apps, or sign in anonymously if that is enabled, so this rule lets every user read and change every other user's data. Compare request.auth.uid with the owner of the document instead.

Do Firebase Storage download URLs respect Security Rules?

No. A download URL from getDownloadURL() contains a token and works for anyone who has the link, whatever your rules say. Treat download URLs as public links and keep private files behind rules that check the signed-in user.

Need help with AI App Security Audit?

Built your app with Lovable, Bolt, Cursor, v0 or Replit? We check it the way an attacker would, with your written OK, and show you what to fix. Tell us what you want to build: we reply within 24 hours with honest advice and a clear quote.

Start a ProjectSee our AI App Security Audit services →

More from our blog