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
- Detect — we identify the Firebase project from
firebaseConfigin the frontend bundle (or from a passed-in URL) - Enumerate — test common collection names (
users,posts,orders,messages,subscriptions,payments,admin,settings) - Probe rules — attempt anon read/write per collection via the Firestore REST API
- Probe storage — list bucket contents and attempt anonymous download
- Probe functions — enumerate
https://<region>-<project>.cloudfunctions.net/*for unauthenticated handlers - Report — grouped findings with severity and copy-paste rule snippets
How attackers find your Firebase project
The recon is mechanical and takes seconds:
- Open your site in a browser, view source, search for
apiKey. The fullfirebaseConfigobject is right there. - From
projectId, attackers know your Firestore endpoint:https://firestore.googleapis.com/v1/projects/<projectId>/databases/(default)/documents/. - They
curlthat URL with no auth and start guessing collection names.users,customers,orders,invoices,subscriptions— all standard. - 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. - For Storage, they hit
https://firebasestorage.googleapis.com/v0/b/<bucket>/oto 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.
Related tools and guides
- Lovable Safety Guide — Lovable apps default to Firebase or Supabase. Same failure pattern, different vendor.
- Token Leak Checker — finds the
firebaseConfig(and worse) in your bundle. - Vibe Code Scanner — full app-level scan that ties Firebase findings back to the page they showed up on.
- Free Security Self-Audit — 30-minute checklist for Firebase + Auth + Storage hardening.
COMMON QUESTIONS
RUN THE FULL FIREBASE AUDIT
The VibeEval agent probes Firestore, Functions, and Storage across every route in your app.