In a sample of 950 open-source GitHub projects with Firebase rules, researchers found that almost 25% had security rules that left data exposed. That is not a theoretical risk — it means one in four Firebase apps was shipping with a wide-open database.
Firebase security rules are the single point of failure between your users' data and the public internet. There is no server sitting between your client app and the database. If the rules are wrong, anyone with your project config (which is public by design) can read or write your data directly.
This guide covers the eight most common Firebase security rule mistakes, with vulnerable code and fixed code for each one.
How Firebase Security Rules Work
Firebase uses declarative rules to control who can read and write data. Every request — from your app or from a REST client — is evaluated against these rules before it touches the database.
There are three rule systems depending on the Firebase product:
Firestore Security Rules use a match and allow syntax:
rules_version = '2';
service cloud.firestore {
match /databases/{database}/documents {
match /users/{userId} {
allow read, write: if request.auth != null;
}
}
}
Realtime Database Rules use a JSON structure:
{
"rules": {
"users": {
"$uid": {
".read": "auth != null",
".write": "auth != null"
}
}
}
}
Storage Rules control file uploads and downloads:
rules_version = '2';
service firebase.storage {
match /b/{bucket}/o {
match /uploads/{userId}/{allPaths=**} {
allow read, write: if request.auth != null;
}
}
}
The critical thing to understand: if you deploy no rules, or deploy rules with allow read, write: if true, your database is completely public. Anyone can read every document and write anything they want.
The 8 Mistakes
1. Leaving Rules Wide Open
The single most common Firebase security mistake is the "fix it later" rule:
Vulnerable:
rules_version = '2';
service cloud.firestore {
match /databases/{database}/documents {
match /{document=**} {
allow read, write: if true;
}
}
}
This grants full read and write access to every document in your database to anyone on the internet. No authentication required. Firebase even shows a warning banner in the console when you deploy this, but developers dismiss it during development and forget to change it before launch.
Fixed:
rules_version = '2';
service cloud.firestore {
match /databases/{database}/documents {
// No default access — every collection must have explicit rules
match /users/{userId} {
allow read: if request.auth != null && request.auth.uid == userId;
allow write: if request.auth != null && request.auth.uid == userId;
}
}
}
The fix is to never use a wildcard {document=**} with open permissions. Define rules per collection, and require authentication at minimum.
2. Only Checking Authentication Without Verifying Ownership
This is the second most common mistake. The rules check that a user is logged in, but not that they own the data they are accessing:
Vulnerable:
match /users/{userId} {
allow read, write: if request.auth != null;
}
Any authenticated user can read and modify any other user's document. If user A is logged in, they can read user B's profile, change their email, or delete their data.
Fixed:
match /users/{userId} {
allow read, write: if request.auth != null && request.auth.uid == userId;
}
The request.auth.uid == userId check ensures users can only access their own documents. This is ownership verification — the single most important pattern in Firebase security rules.
For data that needs to be shared (like a team workspace), use a field-based check:
match /projects/{projectId} {
allow read: if request.auth != null &&
request.auth.uid in resource.data.members;
allow write: if request.auth != null &&
request.auth.uid == resource.data.ownerId;
}
3. Missing Field-Level Validation
Even with ownership checks, users can write arbitrary fields to their own documents if you do not validate the incoming data:
Vulnerable:
match /users/{userId} {
allow read: if request.auth.uid == userId;
allow write: if request.auth.uid == userId;
}
A malicious user could write fields like isAdmin: true, plan: "enterprise", or credits: 999999 to their own profile document. If your app logic reads these fields to gate features, you have a privilege escalation vulnerability.
Fixed:
match /users/{userId} {
allow read: if request.auth.uid == userId;
allow create: if request.auth.uid == userId &&
request.resource.data.keys().hasOnly(['name', 'email', 'avatarUrl']) &&
request.resource.data.name is string &&
request.resource.data.email is string;
allow update: if request.auth.uid == userId &&
request.resource.data.diff(resource.data).affectedKeys()
.hasOnly(['name', 'avatarUrl']);
}
Key points:
- Use
hasOnly()to restrict which fields can be written - Split
writeintocreate,update, anddeletefor granular control - Use
diff().affectedKeys()on updates to restrict which fields can be changed - Never let clients write billing, role, or permission fields
4. Cascading Rules Granting Unintended Access
In Firestore, rules do not cascade from parent to child collections. But in the Realtime Database, they do — and this catches people off guard:
Vulnerable (Realtime Database):
{
"rules": {
"chats": {
".read": true,
".write": "auth != null",
"$chatId": {
"messages": {
"$messageId": {
".write": "auth.uid === newData.child('sender').val()"
}
}
}
}
}
}
The .read: true on chats grants read access to the entire chats tree, including all messages in all chat rooms. The more restrictive rules on child nodes are irrelevant because the parent already granted access.
Fixed:
{
"rules": {
"chats": {
"$chatId": {
".read": "auth != null && root.child('chatMembers').child($chatId).child(auth.uid).exists()",
"messages": {
"$messageId": {
".write": "auth != null && auth.uid === newData.child('sender').val()"
}
}
}
}
}
}
In Realtime Database rules, always put access controls at the deepest level possible, and never set .read: true or .write: true on parent nodes.
In Firestore, the opposite is true: rules on /chats/{chatId} do not apply to /chats/{chatId}/messages/{messageId}. You must write explicit rules for subcollections. This is safer by default, but means you need to remember to add rules for every subcollection.
5. Not Validating Data Types
Firebase stores whatever your client sends. If you expect a number and a user sends a string, your app breaks — or worse, your aggregation logic produces wrong results:
Vulnerable:
match /orders/{orderId} {
allow create: if request.auth != null &&
request.resource.data.userId == request.auth.uid;
}
A user can create an order with { quantity: "abc", price: -100, userId: "their-uid" }. No type checking, no range validation.
Fixed:
match /orders/{orderId} {
allow create: if request.auth != null &&
request.resource.data.userId == request.auth.uid &&
request.resource.data.quantity is int &&
request.resource.data.quantity > 0 &&
request.resource.data.quantity <= 100 &&
request.resource.data.price is number &&
request.resource.data.price > 0 &&
request.resource.data.status == 'pending' &&
request.resource.data.createdAt == request.time;
}
Always validate:
- Types: Use
is string,is int,is number,is bool,is timestamp - Ranges: Check minimum and maximum values for numbers
- Enums: Validate against allowed values with
in ['pending', 'active', 'cancelled'] - Server timestamps: Force
createdAtto userequest.timeso clients cannot forge timestamps
6. Exposing Admin-Only Collections
Many apps store configuration, feature flags, or analytics data in Firebase. These collections often have no rules at all, which means they inherit the default (deny all in Firestore, but may be open in Realtime Database if a parent rule allows it):
Vulnerable:
rules_version = '2';
service cloud.firestore {
match /databases/{database}/documents {
match /users/{userId} {
allow read, write: if request.auth.uid == userId;
}
// No rules for /admin, /config, /analytics
// In Firestore, this means denied by default — but developers
// often "fix" this by adding a wildcard rule
match /{document=**} {
allow read: if request.auth != null;
}
}
}
That wildcard at the bottom gives every logged-in user read access to /admin, /config, and every other collection.
Fixed:
rules_version = '2';
service cloud.firestore {
match /databases/{database}/documents {
match /users/{userId} {
allow read, write: if request.auth.uid == userId;
}
match /admin/{document=**} {
allow read, write: if request.auth != null &&
request.auth.token.admin == true;
}
match /config/{document} {
allow read: if request.auth != null;
allow write: if request.auth != null &&
request.auth.token.admin == true;
}
// No wildcard fallback — unlisted collections are denied
}
}
Use Firebase custom claims (request.auth.token.admin) to gate admin access. Set these claims from your backend using the Admin SDK — they cannot be set by the client.
Never add a wildcard match rule as a "convenience" fallback. Every collection should have explicit rules.
7. Forgetting Storage Rules
Developers spend time on Firestore rules but completely forget about Firebase Storage. The default storage rules in a new project often look like this:
Vulnerable:
rules_version = '2';
service firebase.storage {
match /b/{bucket}/o {
match /{allPaths=**} {
allow read, write: if true;
}
}
}
This means anyone can upload any file (including malicious executables), overwrite other users' files, and read all stored files. Combined with file path enumeration, an attacker can download every file in your storage bucket.
Fixed:
rules_version = '2';
service firebase.storage {
match /b/{bucket}/o {
match /users/{userId}/avatar/{fileName} {
allow read: if request.auth != null;
allow write: if request.auth != null &&
request.auth.uid == userId &&
request.resource.size < 5 * 1024 * 1024 &&
request.resource.contentType.matches('image/.*');
}
match /users/{userId}/documents/{fileName} {
allow read: if request.auth != null &&
request.auth.uid == userId;
allow write: if request.auth != null &&
request.auth.uid == userId &&
request.resource.size < 10 * 1024 * 1024;
}
}
}
Storage rules should always:
- Verify ownership via
request.auth.uid - Limit file size with
request.resource.size - Validate content type with
request.resource.contentType - Use specific paths instead of wildcards
- Separate public files (like avatars) from private files (like documents) with different read rules
8. Not Testing Rules Before Deploy
Firebase provides a Rules Playground in the console and a local emulator for testing rules. Most developers never use either one, deploying rules and hoping for the best.
The risky approach:
# Write rules, deploy, hope it works
firebase deploy --only firestore:rules
The correct approach:
First, use the Firebase Emulator Suite to test rules locally:
// rules.test.js
const { initializeTestEnvironment, assertFails, assertSucceeds } =
require('@firebase/rules-unit-testing');
const testEnv = await initializeTestEnvironment({
projectId: 'my-project',
firestore: {
rules: fs.readFileSync('firestore.rules', 'utf8'),
},
});
// Test: Authenticated user can read own data
const alice = testEnv.authenticatedContext('alice');
await assertSucceeds(
alice.firestore().collection('users').doc('alice').get()
);
// Test: Authenticated user CANNOT read other user's data
await assertFails(
alice.firestore().collection('users').doc('bob').get()
);
// Test: Unauthenticated user cannot read anything
const unauthed = testEnv.unauthenticatedContext();
await assertFails(
unauthed.firestore().collection('users').doc('alice').get()
);
Write tests for every rule. At minimum, test:
- Unauthenticated access is denied
- Users cannot read other users' data
- Users cannot write fields they should not (roles, billing, permissions)
- Invalid data types are rejected
- Admin collections are locked down
You can also use the Rules Playground in the Firebase Console (Firestore > Rules > Rules Playground) to simulate individual requests and see which rule allowed or denied them.
Real-World Impact
Misconfigured Firebase security rules are not a theoretical problem. Here is what happens when rules are wrong:
Data leaks at scale. Security researchers regularly scan the internet for open Firebase databases. In 2024, a study found over 125 million records exposed across thousands of apps, including email addresses, passwords stored in plaintext, GPS coordinates, and financial data. An attacker does not need sophisticated tools — a simple REST request to https://your-project.firebaseio.com/.json returns everything if the Realtime Database rules allow it.
Account takeover. If users can write to each other's documents, an attacker can change another user's email address in the database, trigger a password reset, and take over the account.
Data corruption. Without field validation, a malicious user can write garbage data that breaks your app for everyone. Imagine a shared leaderboard where someone writes score: 999999999 or score: "not a number".
The vibe-coded app problem. A 2025 study by Wiz found that apps built with AI coding tools are particularly vulnerable to Firebase misconfigurations. AI assistants often generate the "quick start" rules (allow read, write: if true) and developers ship them without thinking twice. If you are building with Cursor, Bolt, Lovable, or similar tools, your vibe-coded app likely has security gaps that need manual review.
How to Audit Your Firebase Rules
Manual Review
Open your firestore.rules, database.rules.json, and storage.rules files. Search for these red flags:
if true— wide open accessif request.auth != nullwithout ownership checks — any user can access any data{document=**}with permissive rules — wildcard matches everything- Missing subcollection rules — child documents with no rules
- No field validation on
createorupdate— clients can write anything .read: trueor.write: truein Realtime Database parent nodes — cascading access
Firebase Rules Playground
Use the Rules Playground in the Firebase Console to simulate requests as different users. Test every collection with:
- An unauthenticated request
- A request from a user who does not own the document
- A request with invalid or extra fields
Automated Scanning
Manual review only catches what you think to look for. Automated scanners can detect open Firebase databases, misconfigured storage buckets, and missing authentication from the outside — the same way an attacker would find them.
Check your rules into version control and review them in pull requests, just like application code. Any change to security rules should be reviewed by at least one other developer.
How CheckVibe Helps
CheckVibe's Firebase scanner automatically detects common Firebase security misconfigurations from the outside. It checks for:
- Open Firestore and Realtime Database rules — detects databases that respond to unauthenticated reads
- Missing authentication requirements — identifies endpoints that return data without auth
- Exposed storage buckets — finds Firebase Storage buckets with permissive rules
- Firebase project config exposure — checks if your
firebaseConfigobject leaks information an attacker could use
The scanner runs the same checks an attacker would perform — probing your Firebase endpoints to see what responds. You get a report showing exactly which rules need to be fixed, with severity ratings and fix guidance.
Run a free scan on your Firebase app to see what is exposed.
FAQ
Are Firebase security rules enough to secure my app?
Firebase security rules are your primary defense for client-side data access, but they are not the only layer you need. You should still validate data on the server side (using Cloud Functions or a backend API) for sensitive operations like payments, role assignments, and account deletion. Security rules protect against direct database access, but your Cloud Functions also need proper authentication and input validation. For a more complete security posture, follow the OWASP Top 10 checklist.
What is the difference between Firestore and Realtime Database rules?
Firestore rules use a match/allow syntax and do not cascade — rules on a parent collection do not apply to subcollections. Realtime Database rules use a JSON structure and do cascade — a .read: true on a parent node grants access to all children. Firestore rules are generally considered more secure by default because of this non-cascading behavior. If you are starting a new project, use Firestore.
Can someone access my Firebase data if they have my project config?
Your Firebase project config (apiKey, authDomain, projectId, etc.) is designed to be public. It is embedded in your client-side JavaScript and anyone can find it. The config alone does not grant data access — your security rules determine what is accessible. However, if your rules are permissive, anyone with the project config (which is everyone) can read your entire database. This is why rules are so critical: the config is always public, so the rules are the only barrier.
How do I test Firebase security rules?
Use the Firebase Emulator Suite to write automated tests for your rules. Install it with firebase init emulators, then write tests using @firebase/rules-unit-testing. Test every access pattern: unauthenticated reads, cross-user reads, invalid field writes, and admin operations. Run these tests in CI so rule changes are validated before deploy. You can also use the Rules Playground in the Firebase Console for quick manual testing of individual requests.
Firebase Security Rules Checklist
Use this checklist before every deploy:
- [ ] No
allow read, write: if truerules anywhere - [ ] Every
readandwriterule checksrequest.auth.uidagainst the document owner - [ ]
writerules are split intocreate,update, anddeletewith different permissions - [ ] Field-level validation on
createandupdateusinghasOnly()and type checks - [ ] No wildcard
{document=**}matches with permissive rules - [ ] Admin collections require custom claims (
request.auth.token.admin == true) - [ ] Storage rules validate file size, content type, and ownership
- [ ] Realtime Database rules do not have
.read: trueor.write: trueon parent nodes - [ ] All rules are tested with the Firebase Emulator Suite before deploy
- [ ] Rules are checked into version control and reviewed in pull requests
Firebase security rules are the most important 50 lines of code in your project. Get them wrong and nothing else matters — your database is public. Get them right and you have a solid foundation for securing your API endpoints and the rest of your application.
Want to know if your Firebase app is exposed right now? Run a free CheckVibe scan and get a security report in under 60 seconds. No signup required.