Blog · 2026-08-11
Firebase security rules — the two-minute test that shows whether your Firestore is public
By the howsafeismyapp team
Supabase gets most of the attention in AI-builder discussions, but plenty of Bolt and v0 apps ship with Firebase — and Firestore has its own version of the open-database problem: test mode.
When you create a Firestore database, the console offers "test mode" so development just works. Test mode means: anyone with your project config (which ships in your JavaScript bundle, by design) can read and write everything, usually with an expiry date the generated app never revisits. The result is the same finding we flag on Supabase apps: a database readable without a login. (Practical guidance, not legal advice.)
Why your Firebase config being public is fine — and rules being open is not
Like Supabase's anon key, the Firebase config object (apiKey, projectId, …) is meant to be public. It identifies your project; it doesn't authorize anything. Authorization is entirely the job of security rules. If your rules say allow read, write: if true — or test mode's date-based equivalent — the "api key is public" design becomes "the database is public."
The two-minute test
- Open your deployed app in a private window, DevTools → Network, and note the
projectIdfrom requests tofirestore.googleapis.com. - Logged out of everything, request a collection you know exists via the REST endpoint:
https://firestore.googleapis.com/v1/projects/PROJECT_ID/databases/(default)/documents/users - Documents back = your Firestore answers to strangers. If those documents contain names, emails, or user content, you're looking at an Art. 32 GDPR problem — and if real personal data was exposed, at the breach-notification questions of Art. 33.
Locking it down
In the Firebase console under Firestore → Rules:
- Default deny. Start from
allow read, write: if false;for everything, then open specific paths deliberately. - Per-user data:
allow read, write: if request.auth != null && request.auth.uid == userId;on/users/{userId}— the Firestore equivalent of a Supabase RLS policy. - Public content (a feed, a catalog):
allow read: if true; allow write: if false;— public read can be a product decision; public *write* almost never is. - Re-run the two-minute test. An error or empty response while logged out is success. The inside view (rules editor) and the outside view (what a stranger can fetch) can disagree — the outside view is the one that counts.
If your app uses the Realtime Database instead of Firestore, the same logic applies to its JSON rules — and its test mode is even more permissive.
Firestore rules examples you can copy
Rules are a file, not a checkbox, and most apps need four or five patterns at most. Each block below is a complete firestore.rules you can paste into the console (Firestore → Rules) and adapt.
Locked down — the correct starting point. Nothing is readable or writable from a client; only your server (Admin SDK) and the console can touch the data.
rules_version = '2';
service cloud.firestore {
match /databases/{database}/documents {
match /{document=**} {
allow read, write: if false;
}
}
}
Per-user documents. The workhorse pattern: everyone reads and writes only their own row. Note that the check is on request.auth.uid, which the client cannot forge — not on a userId field in the document body, which it can.
rules_version = '2';
service cloud.firestore {
match /databases/{database}/documents {
match /users/{userId} {
allow read, write: if request.auth != null
&& request.auth.uid == userId;
}
}
}
Documents owned via a field. When the owner is stored inside the document rather than in the path, read/delete check the existing data and create checks the incoming data — a distinction that is easy to get wrong, because resource and request.resource are different things.
match /posts/{postId} {
allow read, update, delete: if request.auth != null
&& request.auth.uid == resource.data.ownerId;
allow create: if request.auth != null
&& request.auth.uid == request.resource.data.ownerId;
}
Public read, authenticated write. A blog, catalog or feed: anyone may read, only signed-in users may add, and only the author may change what they wrote.
match /articles/{articleId} {
allow read: if true;
allow create: if request.auth != null;
allow update, delete: if request.auth != null
&& request.auth.uid == resource.data.authorId;
}
Validating what gets written. Rules can reject malformed or oversized documents, which is worth doing for anything a user types — the client is not a place to enforce this.
match /messages/{messageId} {
allow create: if request.auth != null
&& request.resource.data.text is string
&& request.resource.data.text.size() < 2000
&& request.resource.data.uid == request.auth.uid;
allow read: if request.auth != null;
}
Two habits that save you: rules do not cascade unless you use the {document=**} wildcard, so a match on a parent does not protect its subcollections — write them out. And every change should end with the outside test above, not with the console's green checkmark, because the rules simulator tests what you asked it to test while a stranger tests what you forgot.
Don't stop at the database
Firebase apps from AI builders share the rest of the checklist with everyone else: tracking that fires before consent, missing security headers (test yours in seconds: security headers check), and the legal pages nobody generates. The full pass for your builder: Bolt checklist · v0 checklist.
Common questions
Is my Firebase API key a secret?
No — it ships in your bundle by design and identifying it is not a vulnerability. The things that must stay secret are service-account JSON files and admin SDK credentials; those belong on servers only, never in frontend code: admin key exposed in frontend code.
Firestore warned me about test mode expiring — what happens then?
When the expiry date passes, all client access gets denied and your app simply breaks for users. That's Firebase protecting you from permanent test mode — but the right response is writing real rules, not extending the date another 30 days.
Is Firebase itself GDPR compliant?
It can be operated compliantly: Google offers a DPA, and you should address the US-transfer question in your privacy policy and configure data locations where possible. The practical failures we see aren't Google's paperwork — they're open rules and missing disclosure in the operator's privacy policy.
Check everything at once
Our free passive scan tests your app the way any anonymous browser would — including whether your backend answers without a login — plus 14 other checks. One minute, no signup, nothing stored.