Blog · October 20, 2026 · 11 min read

Firebase Security Rules check: the open rule Firebase won't warn you about

Firebase emails you about test-mode rules, but not about "any signed-in user can read everything", and anyone can sign in. Five checks, with copy-paste commands and rules, to find out whether your Firestore, Realtime Database and Storage are really locked.

If your app runs on Firebase, the web config in your JavaScript (apiKey, projectId, storageBucket and friends) is public. That is by design. Anyone who opens your site can copy it and talk to your database directly, without your app's buttons in the way. The only thing standing between that stranger and your users' data is a short file called Security Rules.

AI builders and coding tools (Firebase Studio, Cursor, Replit, v0, FlutterFlow, Claude Code) are good at getting Firebase working. "Working" usually means the rules let the app read and write, and that is often all they check.

How big is the problem?

In January 2026 a researcher published Firewreck 2.0: he pulled the Firebase config out of the top 1.5 million Android apps, tested them, and found 11,126 insecure Firebase projects exposing about 1.43 billion user records, including apps with 100 million downloads. He emailed about 9,900 developers. A month later, 228 projects had been fixed.

The most useful detail for you is in Google's reply to his report. Firebase sends warning emails and shows a console banner when rules are fully open (if true). But, in Google's words, "there is no console warning for open auth rules": rules that let any signed-in user read or write everything. Google closed the report as intended behaviour.

So a quiet inbox does not mean your rules are safe. Here is how to check them yourself.

The three rules that leak

1. Test mode that never left. When you create a database in test mode, Firebase writes a rule like this:

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

Until that date, anyone on the internet can read, change and delete every document. Firebase emails you before it expires, and the common "fix" (on Stack Overflow, and from AI tools asked to "make the error go away") is to push the date forward. That keeps the database open. The Realtime Database version looks like ".read": "now < 1795046400000", and Storage has its own version too.

2. if true. The fully open rule. Firebase warns about this one, but it still ships, often because an AI tool hit a permission error and took the shortest route.

3. if request.auth != null. This is the one nobody warns you about:

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

It reads as "only logged-in users". But if your app lets people sign up (email and password, Google, or anonymous sign-in), anyone can become a logged-in user in a few seconds, with your public API key and one HTTP request. They don't need your sign-up page. After that they can read every user's documents, not just their own, and often change them too.

The rule you actually want almost always looks like "only the owner of this document": request.auth.uid == userId or resource.data.ownerId == request.auth.uid.

Check 1: Read your rules (2 minutes)

Open the Firebase console and look at every place that has rules:

If you deploy with the Firebase CLI, the files named in firebase.json (usually firestore.rules, database.rules.json, storage.rules) are the source of truth, but check the console too. What is deployed is what counts.

Search for:

Check 2: Ask your database as a stranger (3 minutes)

Get your project ID from the web config in your app. Then try to list a collection with no login at all. Use a collection you know exists, such as users:

curl -s -o /dev/null -w "%{http_code}\n" \
  "https://firestore.googleapis.com/v1/projects/YOUR_PROJECT_ID/databases/(default)/documents/users?pageSize=1"

For the Realtime Database, use the databaseURL from your config:

curl -s -o /dev/null -w "%{http_code}\n" "https://YOUR-DB.firebaseio.com/.json?shallow=true"

401 is good. 200 means the whole database is readable (shallow=true only returns top-level keys, so you won't pull everything down).

For Storage, check whether strangers can list your files:

curl -s -o /dev/null -w "%{http_code}\n" \
  "https://firebasestorage.googleapis.com/v0/b/YOUR_BUCKET/o?maxResults=1"

403 is good. 200 means anyone can list file names, and usually download the files.

Only run these against your own project.

Check 3: Ask again as a fresh, random user (5 minutes)

This is the check that catches rule #3. Create a throwaway account the same way an attacker would, through the public sign-up API, with the web API key from your own config:

curl -s "https://identitytoolkit.googleapis.com/v1/accounts:signUp?key=YOUR_WEB_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"email":"rules-test-1@example.com","password":"a-long-test-password-123","returnSecureToken":true}'

If this returns an idToken, anyone on the internet can create an account in your project, even if your app has no sign-up page. (If you only enabled Google sign-in this call fails, but anonymous sign-in or a Google account gives the same result.) Copy the idToken and repeat Check 2 with it:

curl -s -o /dev/null -w "%{http_code}\n" \
  -H "Authorization: Bearer ID_TOKEN" \
  "https://firestore.googleapis.com/v1/projects/YOUR_PROJECT_ID/databases/(default)/documents/users?pageSize=1"

A brand-new user with no data should get 403 on collections that hold other people's data. A 200 here means any stranger who signs up can read your users. Do the same for orders, messages, profiles and whatever else your app stores. For the Realtime Database, add &auth=ID_TOKEN to the URL.

Delete the test user afterwards in Authentication → Users.

Check 4: Can a user make themselves admin? (3 minutes)

Many AI-built apps keep role: "user", plan: "free" or credits: 10 in the user's own document, and the rules let users update their own document. So a user can change it themselves:

// from any script using your app's public Firebase config, signed in as a normal user
await updateDoc(doc(db, "users", auth.currentUser.uid), { role: "admin", plan: "pro" });

If that works, your paywall and your admin panel are only decoration. In the Firebase console's Rules Playground (on the Rules tab) you can test this without code: simulate an update on /users/<a uid>, signed in as that same uid, with role: "admin" in the data.

Check 5: Storage rules and file size (2 minutes)

Storage test-mode rules have the same expiry trap. Check that files sit under a per-user path and that uploads are limited:

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/.*');
    }
  }
}

Without a size and type limit, any signed-in user can upload gigabytes to your bucket, and you pay for the storage and download traffic.

The fix: owner-only rules you can paste

Here is a solid starting point for a typical app: a users collection keyed by uid, plus a collection of things that belong to one user (notes here, but the same goes for projects, orders or chats).

rules_version = '2';
service cloud.firestore {
  match /databases/{database}/documents {

    function signedIn() { return request.auth != null; }
    function isAdmin() { return signedIn() && request.auth.token.admin == true; }

    match /users/{userId} {
      allow read: if signedIn() && request.auth.uid == userId;
      allow create: if signedIn() && request.auth.uid == userId
        && !request.resource.data.keys().hasAny(['role', 'plan', 'credits']);
      allow update: if signedIn() && request.auth.uid == userId
        && !request.resource.data.diff(resource.data).affectedKeys()
             .hasAny(['role', 'plan', 'credits']);
      allow delete: if false;
    }

    match /notes/{noteId} {
      allow read, delete: if signedIn() && resource.data.ownerId == request.auth.uid;
      allow create: if signedIn() && request.resource.data.ownerId == request.auth.uid;
      allow update: if signedIn() && resource.data.ownerId == request.auth.uid
        && request.resource.data.ownerId == resource.data.ownerId;
    }

    match /{document=**} {
      allow read, write: if isAdmin();
    }
  }
}

What this does:

One thing that will surprise your AI tool: rules are not filters. With these rules, a query for all notes fails with a permission error even for a logged-in user. The query has to ask only for what the user may see: where("ownerId", "==", auth.currentUser.uid). When the app breaks after you tighten the rules, fix the query. Do not loosen the rule.

For the Realtime Database, the same idea looks like this:

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

Should strangers be able to create accounts at all? If your app is invite-only or internal, turn off public sign-up under Authentication → Settings → User actions → Enable create (sign-up). Hiding the sign-up page is not enough, because the API above still works.

Prompt to paste into your AI tool

Rewrite my Firestore, Realtime Database and Storage security rules. Requirements: no if true, no expiry dates, no catch-all match /{document=**} that allows normal users, and never request.auth != null on its own. Every collection must check ownership (request.auth.uid == userId or resource.data.ownerId == request.auth.uid) on read, create, update and delete. Users must not be able to change role, plan, credits or similar fields; those change only server-side. Admin access uses custom claims. Then update every query in the app to filter by the current user's uid so it matches the rules. List each collection, who can read it, and who can write it.

Then check the result yourself with Checks 2 and 3. Don't take the AI's word for it.

Make it stick: test your rules before you deploy

The Rules Playground is good for spot checks. For anything you will keep changing, write a small test against the Firestore emulator using @firebase/rules-unit-testing:

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

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

await env.withSecurityRulesDisabled(async (ctx) => {
  await setDoc(doc(ctx.firestore(), "notes/n1"), { ownerId: "alice", text: "hi" });
});

const alice = env.authenticatedContext("alice").firestore();
const bob = env.authenticatedContext("bob").firestore();
const stranger = env.unauthenticatedContext().firestore();

await assertSucceeds(getDoc(doc(alice, "notes/n1")));
await assertFails(getDoc(doc(bob, "notes/n1")));      // the "any signed-in user" test
await assertFails(getDoc(doc(stranger, "notes/n1")));
await assertFails(setDoc(doc(alice, "users/alice"), { role: "admin" }));

await env.cleanup();
console.log("rules OK");

Run it with firebase emulators:exec --only firestore "node rules.test.mjs". The bob line is the one that matters most: it fails the moment someone (or some AI) "simplifies" your rules back to request.auth != null.

What these rules don't cover

If you use Supabase instead, the same idea is called Row Level Security: see Supabase RLS in plain English. For everything else to check before launch, use the pre-launch security checklist.

Quick FAQ

Is it a problem that my Firebase API key is visible?
No, it is meant to be public. Your rules are what protect the data. Do restrict the key in Google Cloud if other paid APIs are turned on for it.

I got the "expiring in X days" email. Should I extend the date?
No. Extending it keeps your database open to everyone. Replace the rule with owner-based rules before the date.

My rules say request.auth != null. Is that safe?
Only if every signed-in user may see every document, which is rarely true. Anyone can sign up through the API, so treat it as nearly public.

Does Firebase warn me if my rules are insecure?
For fully open and test-mode rules, yes. For "any signed-in user" rules, there is no console warning. You have to check yourself.

Scan your app

Paste your app URL into the free Greenlight Scan. It finds the Firebase config and any leaked keys in your JavaScript. After you verify you own the app, it also tests your Realtime Database, Firestore collections and Storage bucket for access without login, and only counts: it never downloads your documents. The $19 Full Scan unlocks every finding with the exact fix and a prompt to paste into your AI tool. Then run Check 3 above for the signed-in case.

Newsletter

The Friday leak report

One short email a week: the newest ways AI-built apps leak data, and the one-line fix for each. No spam, unsubscribe any time.