What the Firebase rules checker looks for
Your Firebase config ships in your app's JavaScript, so anyone can send requests straight to your database. Security rules are the only thing between that stranger and your data. The checker reads your rules the way Firebase evaluates them and flags each rule a stranger can get through.
| Pattern in your rules | What a stranger can do | Severity |
|---|---|---|
allow read, write: if true;, or allow write; with no condition | Read, change and delete data without an account | Critical |
request.time < timestamp.date(...), the test mode rule | Everything until the date. After it, the app breaks and the quick fix reopens it | Critical, high once expired |
request.auth != null as the only check | Create a free account, then read or edit other users' data | High, critical on a catch-all |
Owner check on request.resource.data only | Take over someone else's document by writing their own uid into it | High |
match /{document=**} or /{allPaths=**} with broad access | Get past every stricter rule in the file | High or critical |
".read": true high in a Realtime Database tree | Read everything below it, whatever the child rules say | Critical at the root |
| Writes with no check on the data | Add fields your app trusts, like a role or a plan | Medium or low |
Two rules of the Firebase engine explain most findings. In Firestore and Storage, when several match blocks cover the same path, a request is allowed if any one of them allows it, so one broad wildcard cancels everything stricter. In the Realtime Database, .read and .write cascade down: once a parent grants access, a false below it has no effect. Firebase documents the first under overlapping match statements and the second in its list of insecure rules.
How to read the result
The verdict on top names the worst thing a stranger can do with these rules. Each finding gives the line, what that stranger can do, and a corrected rule with a copy button. Click a line number to jump to it in the editor.
- Critical: anyone on the internet gets in, with no account, or with one any stranger can create. Fix these first.
- High: a free account or a guessed ID is enough to reach other users' data.
- Medium: access wider than your app needs, often a public read that may be on purpose.
- Low: hygiene, such as a missing
rules_version = '2'or writes with no data checks.
A public read is not always a bug. Published posts or a leaderboard can be public by design. When the checker can't tell, it says so, and the fix shows how to keep the public part public while only the author writes.
How to fix the common problems
Replace test mode and auth != null with an owner check
The test mode rule and the request.auth != null rule many generated apps switch to next both let strangers in. Tie each document to the user who owns it, by its path or by a field it stores.
rules_version = '2';
service cloud.firestore {
match /databases/{database}/documents {
// Owner by path: users/{userId}
match /users/{userId} {
allow read, delete: if request.auth != null && request.auth.uid == userId;
allow create, update: if request.auth != null && request.auth.uid == userId
&& request.resource.data.keys().hasOnly(['name', 'photoUrl']);
}
// Owner by field: the uid stored in each document
match /orders/{orderId} {
allow read: if request.auth != null
&& resource.data.customerId == request.auth.uid;
allow create: if request.auth != null
&& request.resource.data.customerId == request.auth.uid;
}
}
}Then delete the catch-all block, the match with a recursive wildcard that grants access to every path. Without it Firestore refuses everything by default, and each collection opens only where you wrote a rule for it.
Check the stored owner on updates
request.resource.data is what the client sends. If an update rule only checks the uid there, any user can put their own uid into someone else's document and take it over. Check resource.data, the document as stored, and keep the owner field from changing.
allow update: if request.auth != null
&& resource.data.ownerId == request.auth.uid
&& request.resource.data.ownerId == resource.data.ownerId;Realtime Database: grant access at the deepest path
Remove ".read": true and ".write": true from the root and from parent nodes, then compare auth.uid to the key under each user.
{
"rules": {
"users": {
"$uid": {
".read": "auth != null && auth.uid === $uid",
".write": "auth != null && auth.uid === $uid",
".validate": "newData.hasChildren(['name'])"
}
}
}
}Storage: one folder per user, with size and type limits
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;
}Roles, custom claims and shared documents are covered in the Firebase security rules guide.
What a static check can't tell you
This tool reads your rules. It doesn't run them, so keep its limits in mind:
- It can't follow every
get()orexists()lookup or see what your documents hold. A condition it can't judge is left alone rather than flagged. - It doesn't see which rules are deployed. The console, the CLI and a coding agent can each overwrite them.
- It doesn't know your app. A public read may be intended, and a strict rule can still leak through a Cloud Function or API route that uses the Admin SDK, which skips rules.
Prove your rules with tests. The Firebase emulator runs them on your machine, and @firebase/rules-unit-testing lets you send the request a stranger would send and assert that it fails.
import { initializeTestEnvironment, assertFails, assertSucceeds } from "@firebase/rules-unit-testing";
import { doc, getDoc } from "firebase/firestore";
import { readFileSync } from "node:fs";
const env = await initializeTestEnvironment({
projectId: "demo-rules",
firestore: { rules: readFileSync("firestore.rules", "utf8") },
});
const alice = env.authenticatedContext("alice").firestore();
const stranger = env.authenticatedContext("mallory").firestore();
await assertSucceeds(getDoc(doc(alice, "users/alice")));
await assertFails(getDoc(doc(stranger, "users/alice")));
await env.cleanup();Run it with firebase emulators:exec --only firestore "node rules.test.mjs". Then test the live app: the rules in production, and every route around them. Your Firebase API key being visible is expected, as the guide on exposed Firebase API keys explains, which is why the rules carry all the weight.
Questions
Is it safe to paste my rules into this Firebase rules checker?
Yes. The check runs entirely in your browser: the rules you paste never leave the page and nothing is stored. Rules are not secret anyway, since anyone with your public config can probe them, but your project's structure stays with you.
What do Firebase test mode rules do?
Test mode creates a rule that allows every read and write until a set date: request.time < timestamp.date(...) in Firestore, now < ... with a timestamp in milliseconds in the Realtime Database. Until that date anyone with your config can read and change everything. After it every client request is refused, and moving the date forward reopens it all.
Is request.auth != null enough in Firebase security rules?
No. It only proves the request comes from some signed-in account, and sign-up is open by default in Firebase Authentication. Compare request.auth.uid to the document's path or to an owner field, so each user reaches only their own data.
Where can I find good Firestore rules examples?
Every fix this tool suggests is a working example for a common case: owner by path, owner by field, shared documents with a members list, and public reads with owner-only writes. The Firebase security rules guide covers roles and custom claims.
Why does ".read": false not work in my Realtime Database rules?
Realtime Database rules cascade down. If a parent node grants .read or .write, every child inherits it and a false below cannot take it back. Remove the grant from the parent and set it on the deeper paths that need it.
Does a clean result mean nobody can get into my data?
No. It means the checker found no open rule in what you pasted. The deployed rules can differ, lookups depend on real data, and server code using the Admin SDK skips rules. Test the rules in the emulator, then check your live app.