An open Firebase rule means anyone can read your database
Firebase security rules AI app builders inherit a specific failure: a Firestore or Realtime Database whose rules grant open access, so any visitor can read or write the data directly. The dangerous shapes are a rule like allow read, write: if true, and a test-mode rule that grants open access until an expiry date and then gets forgotten.
The reason this matters is that the database is reachable from the browser, not only from your code. Firebase ships its client config (the project ID, the database URL, the web API key) in the page, because the client SDK needs it to connect. So the config is not a secret, and it was never meant to be one. The thing standing between a visitor and your data is the rules, and nothing else. If the rules say if true, the data is open to whoever loads the page or scripts the public REST endpoint.
The observable fact is reachability: whether an unauthenticated request returns data. What that data is worth, and whether a specific record is sensitive, is something only you can judge.
- A rule of allow read, write: if true on Firestore returns and accepts documents for any caller, signed in or not.
- A Realtime Database with .read: true exposes the subtree at its REST path to anyone with the database URL.
- This is an authorization gap (who may read or change which data), not a broken login. The request is anonymous exactly as Firebase allows it to be.
Why AI-scaffolded Firebase projects start open
When you create a Cloud Firestore or Realtime Database, Firebase asks you to pick a starting mode. Test mode writes rules that allow open read and write until a set expiry date, so you can build without fighting permission errors on day one. Locked, or production, mode denies access by default. Per the Firebase docs, test mode is explicitly temporary: the open rules stop working at the expiry timestamp, and the console warns that the database is open while they are live.
AI coding tools optimize for a working demo fast. The generated setup steps almost always reach for test mode, because deny-by-default would make the first read fail and the chat would have to debug rules before showing anything on screen. The app works, the data flows, and the rules question is deferred. Two things then go wrong:
Either way the rules were never tightened to require auth and scope access. The default that got you to a demo is the default that shipped.
- The expiry date passes and reads start failing in production, so someone replaces the rule with allow read, write: if true to make it work again, which is now permanently open.
- The expiry date has not passed yet, the app ships, and the open test-mode rule is still live against real user data.
Check reachability from a logged-out browser
You can confirm whether your database answers unauthenticated callers without any special tooling, against your own project only. Do this from a fresh browser with no Firebase session, or a private window.
For Cloud Firestore, find your project ID (it is in your Firebase config in the client bundle, and in the console). Request a collection through the Firestore REST API at a URL of the form firestore.googleapis.com/v1/projects/PROJECT_ID/databases/(default)/documents/COLLECTION_NAME, with no auth token attached. The safe result is a permission-denied error (HTTP 403). The suspicious result is an HTTP 200 with your documents in the response body, which means the rules are returning data to an anonymous caller.
For the Realtime Database, take your database URL and append a path with .json, for example YOUR-DB.firebaseio.com/some-path.json, opened with no auth. The safe result is a permission-denied response. The suspicious result is your data returned as JSON.
This checks reachability, not whether each record is sensitive. A 200 on a deliberately public collection is fine; a 200 on user records, orders, or messages is not.
- Run the read from a logged-out context. A read that works while you are signed in tells you nothing about anonymous access.
- A 403 or permission-denied is the result you want: rules are refusing the anonymous caller.
- A 200 with real data is the launch blocker. The endpoint is openly readable.
- Do not test write access against production data. Reachability of reads is enough to tell you the rules are open.
Write rules that require auth and scope to the owner
The fix is to replace any open rule with rules that first require authentication and then scope access to the data that caller owns. Authentication is who the caller is; authorization is what that identity is allowed to touch. An open rule fails the second even when the first looks handled, so both have to be explicit in the rule.
In Cloud Firestore, the owner-scoped pattern matches the authenticated user against an owner field or path segment, for example allow read, write: if request.auth != null && request.auth.uid == userId on a per-user document path. In the Realtime Database, the equivalent is .read and .write conditions like auth != null && auth.uid === $uid under a user-keyed node. Start from deny-all and open only the exact paths you intend.
- Never leave a rule at if true, and never ship a test-mode rule. If you only have time for one change, switch to deny-by-default and open paths back up deliberately.
- Require request.auth != null (Firestore) or auth != null (Realtime Database) before any owner check, so anonymous callers are refused first.
- Scope each path to its owner with the uid, rather than one broad rule across the whole database.
- Do not rely on client-side checks. Hiding a screen in the app does not protect the data, because the REST endpoint is reachable directly regardless of your UI.
Server client libraries and Admin SDK calls bypass security rules entirely and authenticate through service credentials. Rules protect the client SDK and public REST surface, which is where browser traffic lives. Keep service-account keys off the client, and never treat a rule as a substitute for validating the contents of a write.
Where VibeCodeGuard fits, and what it does not prove
A focused Launch Check probes your Firebase REST endpoints with unauthenticated requests and reports whether Firestore or Realtime Database access is open. It flags the reachability signal from the outside, the same anonymous-read result the manual check above produces, so an open database does not slip past you on launch day.
Writing correct rules is your work inside Firebase. The scan tells you the door is open; it does not decide which rooms should be locked, and it cannot judge whether a readable collection is meant to be public. It is not a penetration test, a code review, or an authorization audit, and it does not make your app secure or guarantee anything about rules it cannot see from the public surface.
Reachability is observable from outside; sensitivity and intent are not. A clean Launch Check means an anonymous read was refused at the endpoints probed, not that every rule across your project is correct. Pair it with reading your own rules, then run a focused Launch Check before sharing the URL publicly.
> launch check
Scan the public surface before launch.
Get severity, evidence, and practical fix guidance for the checks VibeCodeGuard can run from the outside.