Guides / firebase-security-rules-checklist-before-launch
Firebase Security Rules Checklist Before Launch
What Firebase Security Rules actually protect, the mistakes AI builders keep shipping, a pre-launch checklist, and how to prove your data isn't readable by strangers.
What Security Rules are, in plain words
A Firebase app usually talks to its database straight from the browser. There is no backend of yours in between, so something has to decide whether each request is allowed. That something is Security Rules: small rule files that Firebase checks on every read and write coming from a client SDK.
- Cloud Firestore uses
matchblocks for document paths andallow read, write: if <condition>statements. You can splitreadintogetandlist, andwriteintocreate,updateanddelete. - Realtime Database uses a JSON tree of
.read,.writeand.validaterules. Rules cascade: once a parent path grants read or write, a child rule cannot take it away. - Cloud Storage has its own, separate rules file. Locking down Firestore does nothing for uploaded files.
Your Firebase web config (including apiKey) is public by design, and anyone can copy it out of your JavaScript. Rules are the only thing standing between that config and your data. Code using the Admin SDK on a server bypasses rules entirely, so keep service account credentials off the client.
Common vibe-coding mistakes
- Open rules.
allow read, write: if true;in Firestore or".read": true, ".write": truein Realtime Database. Anyone with your project ID can dump or wipe the data. AI tools often suggest this to "fix" a permission-denied error. - Test mode that never got replaced. Firestore test mode allows everything until a date in the rule. Until then your database is public; afterwards the app breaks and someone pastes in
if true. auth != nullwith no ownership check. That only means "someone is signed in". If sign-up is open or anonymous auth is enabled, any stranger is signed in too, and can read every user's documents.- No validation. Even owner-only writes let a user set
role: "admin",credits: 999999or a 1 MB "name" unless rules check fields, types and sizes. - Open Storage. Storage rules left at
allow read, write: if true, orif request.auth != nullon every path, so users can read each other's files and upload anything of any size. - Emulator rules vs production rules. The Local Emulator Suite runs the rules in your local
firestore.rules,database.rules.jsonandstorage.rulesfiles. Passing tests locally means nothing until you deploy them (for examplefirebase deploy --only firestore:rules,database,storage), and rules edited in the console can be overwritten by the next deploy of an old file.
Pre-launch checklist
- Logged-out access: no
if trueon private data in Firestore, Realtime Database or Storage. Anything public (a product catalog, published posts) should be public on purpose, read-only, and contain nothing sensitive. - Per-user ownership: private documents check
request.auth.uidagainst the path (/users/{userId}) or an owner field. Remember that rules are not filters: a Firestore list query must include the same condition (for examplewhere("ownerId", "==", uid)) or it will be denied. - Field validation: creates and updates restrict which keys may be written, check types and lengths, and stop users changing
ownerId, roles, balances or anything only your server should set. - Storage content type and size: uploads are limited to the owner's folder, a maximum size, and the content types you expect. The content type is declared by the client, so treat it as a sanity check, not proof the file is safe.
- Deployed rules match your repo: compare the Rules tab in the console with your local files after your final deploy.
- Optional hardening: App Check. App Check (reCAPTCHA Enterprise or reCAPTCHA v3 on the web) helps block requests that don't come from your real app. Turn on enforcement only after its metrics show your legitimate traffic is verified. It complements rules; it never replaces them.
Starter rule examples (customize before use)
Warning: these are starting points for one common shape of data, not rules you can copy and ship. Your collections, field names and sharing model are different. Rename paths and fields, decide what should really be public, and test every allow and deny case in the Rules Playground or emulator before deploying. There is no generic rule that is safe for every app, and any rule containing if true on private data is not safe at all.
Firestore: each user owns /users/{userId}, and posts carry an ownerId field. Paths not matched by a rule are denied.
rules_version = '2';
service cloud.firestore {
match /databases/{database}/documents {
match /users/{userId} {
allow read, write: if request.auth != null && request.auth.uid == userId;
}
match /posts/{postId} {
allow read: if request.auth != null && resource.data.ownerId == request.auth.uid;
allow create: if request.auth != null
&& request.resource.data.ownerId == request.auth.uid
&& request.resource.data.keys().hasOnly(['ownerId', 'title', 'body'])
&& request.resource.data.title is string
&& request.resource.data.title.size() <= 200
&& request.resource.data.body is string
&& request.resource.data.body.size() <= 5000;
allow update: if request.auth != null
&& resource.data.ownerId == request.auth.uid
&& request.resource.data.ownerId == resource.data.ownerId
&& request.resource.data.keys().hasOnly(['ownerId', 'title', 'body']);
allow delete: if request.auth != null && resource.data.ownerId == request.auth.uid;
}
}
}
Realtime Database: each user reads and writes only their own node. The root has no .read or .write, so everything else is denied, and the $other rule rejects unexpected fields.
{
"rules": {
"users": {
"$uid": {
".read": "auth != null && auth.uid === $uid",
".write": "auth != null && auth.uid === $uid",
"displayName": { ".validate": "newData.isString() && newData.val().length <= 100" },
"$other": { ".validate": false }
}
}
}
}
Cloud Storage: users upload images up to 5 MB into their own folder. Deletes are allowed separately because a delete has no incoming file to check.
rules_version = '2';
service firebase.storage {
match /b/{bucket}/o {
match /users/{userId}/{fileName} {
allow read: 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/.*');
allow delete: if request.auth != null && request.auth.uid == userId;
}
}
}
How to test your rules
Inside Firebase: open the Rules tab for Firestore, Realtime Database or Storage in the Firebase console and use the Rules Playground. Simulate a read or write on a real path, first unauthenticated, then as user A on user A's data, then as user A on user B's data. You want allow, allow, deny, and the same for writes with bad fields. For repeatable checks, write tests against the Local Emulator Suite and run them before every rules deploy.
From the outside: the Playground tests the rules you typed, not necessarily what your live app exposes. Open your deployed app in a private window without logging in and see what data appears. Then run the free PreflightVibe scan on your live URL: if your Firebase config is in the public bundle, it checks whether your Realtime Database, Storage listing and Firestore collections named in your code are readable without logging in. It only reads, and it never stores document contents. Because it doesn't log in, it can't test user A reading user B's data, so do that two-account check by hand.
Want the wider picture? See what our security checker for vibe coded apps covers beyond Firebase, and if part of your stack runs on Supabase, read the Supabase RLS checklist.
FAQ
- Is my Firebase apiKey a secret?
- No. It identifies your project and is meant to ship in the browser. Your Security Rules decide who can read and write.
- Do Firestore test mode rules expire?
- They allow everything until a date written into the rule, then deny all client requests. Neither state is launch-ready; replace them with real rules.
- Is
auth != nullenough? - Usually not. If anyone can sign up or sign in anonymously, it is close to public. Add an ownership check like
request.auth.uid == userId. - Does App Check replace Security Rules?
- No. It makes abuse from scripts harder but doesn't decide which user may read which data. Use it as optional extra hardening.
- How can I tell if my Firebase data is public?
- Open the app logged out, test unauthenticated reads in the Rules Playground, and run the free PreflightVibe scan, which checks anonymous Firebase reads from the outside.
Check whether your Firebase data is readable logged out
Paste your live URL. In about 30 seconds the free scan grades your app A–F and flags open Firebase data, leaked keys, exposed files and missing headers. Unlock the full report with exact Firebase rules fixes for $29 when you are ready, or add a $12/mo weekly re-check.
Run the free scan