FIREBASE SECURITY SCANNER

Test your Firebase-backed app for open Firestore reads, permissive Cloud Storage, and config leaks. Built for AI-generated Firebase projects that ship fast.

TEST YOUR FIREBASE PROJECT NOW

Enter your deployed Firebase app URL — we extract the config from the bundle, then probe Firestore rules, Storage buckets, and Functions endpoints.

Why Firebase Gets Left Open

Firebase quickstarts ship with allow read, write: if true; in the rules file. AI-generated apps inherit that pattern and deploy to production. The Firebase console shows traffic, the app works in the browser, no errors in logs — and meanwhile every row in users, every document in messages, every file in the default bucket is readable by anyone with the URL.

The failure mode isn’t a missing feature — it’s that the insecure default looks identical to the secure default in the dashboard. Until something gets exfiltrated, you have no signal.

What Gets Tested

FIRESTORE RULES

Per-collection read/write tests. Flags anon-readable sensitive data and `if true` shortcuts.

CLOUD STORAGE

Bucket-level access. Lists files that anyone can download via the public download URL pattern.

CLOUD FUNCTIONS

HTTP triggers accessible without authentication or rate limits, plus callable functions that skip context.auth checks.

CONFIG EXPOSURE

Firebase SDK config in bundle — fine alone, risky with bad rules. Flags service-account JSON if accidentally bundled.

APP CHECK COVERAGE

Whether App Check is enforced on Firestore, Storage, and Functions — the rate-limit / abuse layer most apps skip.

AUTH PROVIDERS

Anonymous sign-in left enabled (becomes a free identity for attackers), self-signup with no email verification, and weak password policy.

REALTIME DATABASE

Legacy RTDB instances still attached to the project — different rules engine, often forgotten when migrating to Firestore.

FCM / SERVER KEYS

Cloud Messaging server keys and VAPID keys leaked in the bundle — lets anyone push notifications to your users.

How It Works

  1. Detect — we identify the Firebase project from firebaseConfig in the frontend bundle (or from a passed-in URL)
  2. Enumerate — test common collection names (users, posts, orders, messages, subscriptions, payments, admin, settings)
  3. Probe rules — attempt anon read/write per collection via the Firestore REST API
  4. Probe storage — list bucket contents and attempt anonymous download
  5. Probe functions — enumerate https://<region>-<project>.cloudfunctions.net/* for unauthenticated handlers
  6. Report — grouped findings with severity and copy-paste rule snippets

How attackers find your Firebase project

The recon is mechanical and takes seconds:

  1. Open your site in a browser, view source, search for apiKey. The full firebaseConfig object is right there.
  2. From projectId, attackers know your Firestore endpoint: https://firestore.googleapis.com/v1/projects/<projectId>/databases/(default)/documents/.
  3. They curl that URL with no auth and start guessing collection names. users, customers, orders, invoices, subscriptions — all standard.
  4. If a collection is open, they paginate. The Firestore REST API returns up to 1000 docs per page with pageToken-based pagination. A single bash loop dumps a million-row collection in minutes.
  5. For Storage, they hit https://firebasestorage.googleapis.com/v0/b/<bucket>/o to list files, then GET each by name.

There’s no rate limit in front of this for an unauthenticated read on an open rule. The scanner does the same recon, but stops at “exists / is readable” and never paginates real data.

Firestore rules cheat sheet

Pattern Verdict Why
allow read, write: if true; Critical Public read/write. Game over.
allow read: if true; on PII Critical Profiles, emails, phone numbers all enumerable.
allow read, write: if request.auth != null; High Any signed-in user reads everyone else’s data. Anon sign-in makes this anonymous-equivalent.
allow read: if request.auth.uid == resource.data.userId; Pass Row-level — caller can only read their own rows.
allow write: if request.auth.uid == request.resource.data.userId; Pass Use request.resource (the new doc) on writes, not resource (current doc).
allow read: if request.auth.token.admin == true; Pass with caveat Custom claims — make sure the claim is set server-side only.
match /{document=**} with broad rules High Wildcard cascades to every subcollection — easy to over-grant.

Fix Recipes

Lock down a collection (per-user row-level):

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

    match /users/{userId} {
      allow read: if request.auth.uid == userId;
      allow write: if request.auth.uid == userId
                   && request.resource.data.role == 'user';
    }

    match /posts/{postId} {
      allow read: if resource.data.public == true
                  || request.auth.uid == resource.data.authorId;
      allow create: if request.auth.uid == request.resource.data.authorId;
      allow update, delete: if request.auth.uid == resource.data.authorId;
    }

    // Default deny — required to catch missed collections
    match /{document=**} {
      allow read, write: if false;
    }
  }
}

Lock down Storage:

// storage.rules
rules_version = '2';
service firebase.storage {
  match /b/{bucket}/o {
    match /users/{userId}/{allFiles=**} {
      allow read: if request.auth.uid == userId;
      allow write: if request.auth.uid == userId
                   && request.resource.size < 5 * 1024 * 1024
                   && request.resource.contentType.matches('image/.*');
    }
    match /{allPaths=**} {
      allow read, write: if false;
    }
  }
}

Enforce App Check on Functions:

// functions/index.js
import { onCall, HttpsError } from 'firebase-functions/v2/https';

export const createOrder = onCall(
  { enforceAppCheck: true },
  async (request) => {
    if (!request.auth) throw new HttpsError('unauthenticated', 'login required');
    if (request.app == undefined) throw new HttpsError('failed-precondition', 'app check required');
    // ...
  }
);

Disable anonymous sign-in if you don’t actively need it: Authentication → Sign-in method → Anonymous → Disable. Otherwise every scanner, scraper, and bot becomes a request.auth != null identity in your rules.

What this scanner does NOT flag

Calibration — to keep the false-positive rate low, the scanner explicitly skips:

  • Custom-claim-gated collections that look open at first glance because the scanner has no claims to test with. If a collection rejects anon reads but allows request.auth.token.admin == true, we mark it Pass and recommend manual review.
  • Functions behind a secret-token query param. Security-by-obscurity, but we can’t tell from the outside whether the secret is rotated. Reported as Info, not High.
  • Storage rules that allow listing but block individual file reads. Listing is itself a leak (filenames often contain user IDs and timestamps), but we surface it as Medium, not Critical.
  • Realtime Database instances under custom domains. If you’ve fronted RTDB with a reverse proxy, we won’t auto-discover it — pass the URL explicitly.

For the inverse — false negatives that the scanner can miss — see the “Common Firestore rules pitfalls” section in the vibe code scanner writeup.

COMMON QUESTIONS

01
What does the Firebase scanner check?
Firestore security rules (anon read/write on collections), Cloud Storage bucket permissions, Cloud Functions accessible without auth, Firebase config exposure in the frontend, Firebase Auth anonymous sign-in abuse vectors, and App Check coverage.
Q&A
02
Does the scan make any writes to my database?
No. The scanner only performs reads and schema enumeration. No mutations, no deletes, no Cloud Function invocations beyond safe GETs.
Q&A
03
How do I fix an open Firestore rule?
Replace `allow read, write: if true;` with `allow read: if request.auth.uid == resource.data.userId;` (or equivalent). Deploy rules via Firebase CLI, then re-scan to verify.
Q&A

RUN THE FULL FIREBASE AUDIT

The VibeEval agent probes Firestore, Functions, and Storage across every route in your app.

START FULL SCAN