VibeSecurity

Guide

Firebase security rules check: audit and fix before launch

Firebase web apps talk to the database from the browser, so your security rules are the only thing between the internet and your data. In this guide you will read all three rule sets, remove open or test-mode rules, write rules that check who the user is, and test them with the emulator before deploying. You will finish knowing that a signed-out visitor and a different user are both refused.

By the VibeSecurity team6 min read

What you need and what you will have at the end

You need owner or editor access to your own Firebase project, the Firebase CLI installed and logged in, and your rules files in the repository. If your rules only exist in the console today, copy them into files first so they are versioned. Only test projects you own.

At the end you will have deny-by-default rules for every Firebase product you use, user-scoped access on each collection, tests that fail if the rules become open, and a live check from outside your app.

The Firebase config is not the secret

The apiKey and project identifiers in your Firebase web config are not secrets. They identify your project so the client libraries know where to send requests. Rules decide what a request can do, so hiding the config gives no protection, and open rules cannot be fixed by hiding it.

Firebase also documents App Check, which verifies that requests come from your genuine app. It protects you from abuse by other clients, and it complements rules rather than replacing them. It says itself that it prevents some but not all abuse vectors, so rules stay the primary control.

Step 1: Read all three rule sets

Firebase Security Rules are defined separately for each product. Firestore and Cloud Storage use one rule language, and Realtime Database uses JSON. Rules across products do not share protection, so open the console and read each one you use: Firestore Database > Rules, Storage > Rules, and Realtime Database > Rules.

Pull the current versions into your repository so you can review them as files. The CLI writes them to the paths named in firebase.json when you run firebase init, and you can copy the console text in by hand if that is quicker.

Step 2: Spot rules that mean your data is open

Look for these patterns. They allow access to everyone, or to everyone until a date passes, at which point the app breaks and a panicked fix often makes it open again. Firebase's documentation warns against rules that allow anyone to overwrite the entire database.

In Realtime Database rules, a top-level .read or .write set to true has the same effect. Realtime Database rules cascade, so a permissive rule at a shallow path grants access to everything beneath it even if a deeper rule says false.

Firestore rules to remove
match /{document=**} {
  allow read, write: if true;
}

match /{document=**} {
  allow read, write: if request.time < timestamp.date(2026, 10, 1);
}

Step 3: Write Firestore rules that check the user

Start from deny-all. In Firestore, anything not matched by an allow rule is refused, so you only write what each collection needs. Rules are applied as OR statements, so if any matching rule grants access, the request succeeds. That means a broad rule anywhere can undo your careful ones.

The example lets a signed-in user manage their own profile document and read, create, update and delete only orders that carry their own uid. On create, it checks the incoming data, so a user cannot write an order that claims another owner. On update, it checks both the stored document and the new version so the owner field cannot be changed.

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

Step 4: Fix Cloud Storage and Realtime Database rules

Apply the same idea to the other products. For Storage, scope files by a path segment that holds the user id, and check it against request.auth.uid. For Realtime Database, use auth.uid in a per-user path. Firebase's database documentation shows this pattern with a write rule that compares the path variable to auth.uid.

storage.rules and database.rules.json
rules_version = '2';
service firebase.storage {
  match /b/{bucket}/o {
    match /users/{userId}/{allPaths=**} {
      allow read, write: if request.auth != null && request.auth.uid == userId;
    }
  }
}

{
  "rules": {
    "users": {
      "$uid": {
        ".read": "auth != null && auth.uid === $uid",
        ".write": "auth != null && auth.uid === $uid"
      }
    }
  }
}

Step 5: Test with the emulator before you publish

Firebase provides two ways to test: the Rules Playground in the console for a quick check, and the Local Emulator Suite for repeatable tests you keep in the repository. Use both. In the Playground, simulate a signed-out read, then an authenticated read as a user who does not own the document. Both should be denied, and the owner's read should be allowed.

For a repeatable version, start the emulator and run rule tests against it. The Firebase docs describe unit testing rules against the emulator, and the test cases are the part worth keeping: one that a signed-out request is refused, one that another user is refused, and one that the owner is allowed.

Terminal
npm install --save-dev firebase-tools @firebase/rules-unit-testing
firebase emulators:start --only firestore

Step 6: Deploy and re-check the live project

Deploy only the rules, not the whole project, so a mistaken hosting change does not ride along. Firebase notes that rule changes can take up to 10 minutes to reach active listeners, so wait before the final check.

Then test from outside your app. Firestore exposes a REST endpoint per project, and a signed-out request to it should be refused for any private collection. A permission denied response is the pass condition. A JSON document in the body means your live rules are open.

Terminal
firebase deploy --only firestore:rules,storage

curl -s "https://firestore.googleapis.com/v1/projects/<project-id>/databases/(default)/documents/orders?pageSize=1"
curl -s "https://<database-name>.firebaseio.com/users.json"

Common mistakes

  • Checking only request.auth != null. That lets any signed-in user, including one who just signed up, read everyone's documents.
  • Fixing Firestore and forgetting Storage or Realtime Database, which have separate rule sets.
  • Leaving a date-based test-mode rule in place until the app breaks, then extending the date instead of writing real rules.
  • Trusting a role or owner field that the client wrote itself. Compare against request.auth.uid or a claim the server set.
  • Assuming rules protect server-side code. Firebase's server client libraries bypass rules and rely on IAM instead, so your server must check ownership itself.
  • Expecting a rule at a deeper Realtime Database path to override a permissive rule above it. Rules there cascade downward and cannot be revoked lower down.

How to verify

Pass conditions
CheckHowPass condition
No open rulesSearch rules files for if true and date-based conditionsNo matches
Signed-out refusedPlayground or emulator test, and live REST callPermission denied
Other user refusedSimulate a different uid reading another user's documentPermission denied
Owner allowedSimulate the owner reading and writing their dataAllowed
All products coveredRead Storage and Realtime Database rules separatelyEach has user-scoped rules
Live matches repoCompare console text with your rules filesIdentical

Keep it working

Run the emulator rule tests in CI so a loosened rule fails the build. Re-read your rules whenever you add a collection, storage path or database node, and whenever an AI tool edits the rules file. Deploy rules from the repository rather than pasting into the console, so the file you reviewed is the file that is live. Consider App Check once rules are correct, as an extra layer against clients that are not your app.

Frequently asked questions

Are test mode rules safe for a short launch window?

No. Test mode is meant for early development and leaves data readable and writable by anyone with your project config, which is public in a web app. Replace it with user-scoped rules before real users sign up.

Do Firestore rules also cover Cloud Storage?

No. Firestore, Cloud Storage and Realtime Database each have their own rules. A tight Firestore ruleset says nothing about who can read your storage bucket, so audit all three if you use them.

Is App Check enough on its own?

No. App Check verifies that a request comes from your genuine app, while rules decide what a signed-in user may do. Firebase says App Check prevents some but not all abuse vectors. Use it in addition to correct rules, not instead of them.

How do I know my deployed rules match what I tested?

Deploy from your repository with the Firebase CLI, wait for propagation, then repeat a signed-out request against the live project. If the emulator tests and the live check agree, the deployed rules are doing what you tested.

Sources

  1. 1.Firebase docs: Get started with Cloud Firestore Security Rules
  2. 2.Firebase docs: Security Rules
  3. 3.Firebase docs: Realtime Database Security Rules
  4. 4.Firebase docs: Getting started with Security Rules
  5. 5.Firebase docs: App Check