pwnmyvibecode_

Firebase security rules: the part that actually protects your data

Your Firebase apiKey only identifies your project, and it ships to every visitor. Firestore, Realtime Database and Cloud Storage security rules decide who can read and write each document or file, so rules that allow everything (if true, or an expired test mode rule replaced with if true) leave your data open to anyone.

Updated

Check your live site now

Free, no signup, read only. A grade and plain fixes in seconds.

Why the Firebase apiKey is not the secret

Every Firebase web app has a firebaseConfig object with an apiKey that starts with AIza. It sits in your JavaScript, and Firebase's own documentation says that is fine. The key tells Google which project your app belongs to. It does not grant access to your data.

That means anyone who opens your site has everything they need to talk to your Firestore database or Storage bucket directly, with their own script instead of your app. What stops them is security rules. Firebase checks the rules on every request from a client SDK or the REST API, and rejects anything the rules do not allow.

Server code using the Admin SDK with service account credentials does bypass rules. A service account JSON file contains a private key. That file is the real secret, and it must never be in your front end or your public repo.

Firestore test mode rules and why if true is dangerous

When you create a Firestore database in the console, you choose production mode (deny everything) or test mode. Test mode writes a rule that allows all reads and writes until a date roughly 30 days out:

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

Until that date, anyone with your project ID can read, overwrite or delete every document. When the date passes, every client request fails and the app breaks. The quick fix people reach for, and the one an AI agent will often suggest, is to change the condition to if true. That removes the expiry and keeps the database open forever.

firestore.rules
// Do not ship this
match /{document=**} {
  allow read, write: if true;
}

A related trap is if request.auth != null. It looks safer, but it only checks that the caller is signed in to some account. If your app lets anyone sign up, or has anonymous sign in enabled, a stranger can create an account in seconds and then read everything. Rules need to check which user is asking, not just that someone is.

Example Firestore rules using request.auth

Write rules per collection, and match on the user id. These rules give each user a private profile document they can edit, but not the fields that control permissions, and let authors manage their own posts:

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

    // One profile per user, stored at users/{uid}
    match /users/{userId} {
      allow read: if request.auth != null && request.auth.uid == userId;
      allow create: if request.auth != null
        && request.auth.uid == userId
        && !('role' in request.resource.data);
      // Users may change their name and photo, never role or plan
      allow update: if request.auth != null
        && request.auth.uid == userId
        && request.resource.data.diff(resource.data).affectedKeys()
             .hasOnly(['displayName', 'photoURL']);
      allow delete: if false;
    }

    // Published posts are public, drafts are visible to their author
    match /posts/{postId} {
      allow read: if resource.data.published == true
        || (request.auth != null && resource.data.authorId == request.auth.uid);
      allow create: if request.auth != null
        && request.resource.data.authorId == request.auth.uid;
      allow update: if request.auth != null
        && resource.data.authorId == request.auth.uid
        && request.resource.data.authorId == resource.data.authorId;
      allow delete: if request.auth != null
        && resource.data.authorId == request.auth.uid;
    }
  }
}
  • request.auth holds the signed in user, or null. request.auth.uid is their Firebase Auth user id.
  • resource.data is the document as it is stored now. request.resource.data is the document as it would be after the write.
  • Any collection you do not match is denied. Leave out the match /{document=**} catch all.
  • Rules are not filters. A query for all posts fails outright if it could return a draft the user cannot read. Your query has to include the same condition, such as where published == true.

Realtime Database uses JSON rules instead, with the same idea: "users": { "$uid": { ".read": "$uid === auth.uid", ".write": "$uid === auth.uid" } }. A root level ".read": true or ".write": true opens the whole database.

Example Cloud Storage rules for user uploads

Cloud Storage has its own rules file, separate from Firestore. An open Storage bucket exposes every uploaded file, which for many apps means ID photos, documents or private images. Give each user a folder and check the path:

storage.rules
rules_version = '2';
service firebase.storage {
  match /b/{bucket}/o {
    // Files live at users/{uid}/...
    match /users/{userId}/{allPaths=**} {
      allow read, delete: if request.auth != null && request.auth.uid == userId;
      allow create, update: if request.auth != null
        && request.auth.uid == userId
        && request.resource.size < 5 * 1024 * 1024
        && request.resource.contentType.matches('image/.*');
    }
  }
}

The size and content type checks stop someone from using your bucket as free file hosting. Adjust both to what your app actually accepts.

How to test Firebase security rules

The Rules Playground in the Firebase console (open the Rules tab for Firestore, Realtime Database or Storage) simulates a single request. Pick get, create, update or delete, enter a path, toggle authentication and set a uid, and it tells you whether the request is allowed and which line decided. Use it to try the requests an attacker would: reading another user's document, writing a role field, reading while signed out.

For rules you will keep changing, write tests against the Firebase Emulator Suite with the @firebase/rules-unit-testing package and run them with firebase emulators:exec. They run locally and never touch your real data:

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

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

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

await assertSucceeds(getDoc(doc(alice, "users/alice")));
await assertFails(getDoc(doc(alice, "users/bob")));
await assertFails(getDoc(doc(stranger, "users/alice")));

You can also check from outside, against your own project only. An unauthenticated request to the Firestore REST API for a collection should come back with PERMISSION_DENIED. If it returns documents, that collection is public:

terminal
curl "https://firestore.googleapis.com/v1/projects/YOUR_PROJECT_ID/databases/(default)/documents/users"

What Firebase App Check adds

App Check makes clients prove they are your real app before Firestore, Realtime Database, Cloud Storage and other supported services answer. On the web it uses reCAPTCHA Enterprise or reCAPTCHA v3. On mobile it uses Play Integrity on Android and App Attest or DeviceCheck on Apple platforms. Once you turn on enforcement for a service in the console, requests from plain scripts using your apiKey are rejected.

App Check cuts down on scripted abuse, but it does not replace rules. Someone can still use your real app in a real browser and make any request your rules allow. Watch the App Check metrics in the console for a while before you enforce, so you do not lock out real users on old app versions, and use the debug provider for local development.

How to restrict your Firebase API key in Google Cloud

The apiKey is not a secret, but an unrestricted key can be used from anywhere for any API enabled on the project, and some of those cost money. Restricting it is cheap:

  1. Open Google Cloud console, pick your Firebase project, and go to APIs & Services, then Credentials.
  2. Open the key whose value matches the apiKey in your firebaseConfig.
  3. Under Application restrictions, choose Websites and add your domains, your authDomain (usually YOUR_PROJECT.firebaseapp.com) if you use Firebase Auth, and localhost for development.
  4. Under API restrictions, choose Restrict key and select only the APIs your app uses.
  5. Save, then test sign in and every data call in your app. Some Firebase features need specific APIs allowed.

Referrer restrictions are enforced by Google checking the Referer header, which a script can fake, so treat them as abuse control. Rules and App Check are still the lock.

What an outside scan can check on a Firebase app

Our scan reads your live site with ordinary page requests. It cannot read or test your Firebase security rules, since that would require querying your database. It does flag Google API keys (AIza...) in your page or bundles that work with no restrictions, private keys such as a service account key shipped to the browser, other leaked secret keys, exposed .env and .git files, and missing security headers. For the rules themselves, use the Rules Playground, the emulator tests and the curl check above.

Questions

Is it safe to expose my Firebase apiKey?

Yes. Firebase web config keys are meant to ship in your front end. Your data is protected by security rules and optionally App Check, not by hiding the key. Restricting the key in Google Cloud console is still worth doing.

What happens when Firestore test mode expires?

The rule's date check starts failing, so every client read and write is denied and the app stops working. Replace it with real rules per collection. Do not change it to if true.

Is allow read, write: if request.auth != null secure?

Only if you control who can create accounts. On an app with open sign up, any stranger can register and then read and write everything. Check request.auth.uid against the document's owner instead.

Do security rules apply to the Admin SDK?

No. The Admin SDK and service account credentials bypass rules entirely. Keep them on your server, and rotate the service account key if it was ever exposed.

Do Firestore and Storage share the same rules?

No. Firestore, Realtime Database and Cloud Storage each have their own rules. Locking down Firestore does nothing for an open Storage bucket, so check all three.