Firebase security rules: fix test mode, if true and open access
Firebase security rules left in test mode or set to allow read, write: if true let anyone with your project ID read, change or delete your data. Require sign-in and match request.auth.uid (the signed-in user's ID) to an owner field, a field on each document that stores its creator's ID. Don't extend the test mode date. Prove your rules in the Firebase emulator, a local copy of Firebase.
QUICK CHECKLIST
- Search your rules for
if trueand".read": true - Replace test mode with owner rules on request.auth.uid
- Never push the test mode date out again
- Make every query carry the constraint its rule requires
- Keep admin flags out of client-writable documents
- Run emulator tests before every deploy
What Firebase security rules do and why test mode is risky
Firebase security rules decide who can read and write your Firestore documents, Realtime Database nodes and Cloud Storage files. Your app talks to Firebase straight from the client with no server of yours in between, so the rules are the only gate.
When you create a database or bucket, the console asks you to pick Locked mode (deny everyone) or Test mode (allow everyone). Test mode is fine for an afternoon. Google's docs warn that without authentication and rules, anyone who guesses your project ID can steal, modify or delete the data.
How open Firebase rules get abused
Your Firebase web config sits in your frontend bundle, and anyone can copy the project ID from dev tools. That's by design, the same way NEXT_PUBLIC_ environment variables are public by design. The rules do all the protecting.
With allow read, write: if true, an attacker needs no account. They send requests to Firebase's REST API (plain web requests) or use the SDK with your project ID, then read any collection whose name they can find. For Realtime Database, the docs show the REST address as https://<database_name>.firebaseio.com/path/to/my/data.json.
Writes work the same way. Google's own comment on that ruleset says it lets anyone overwrite your entire database. Open Storage rules expose every file in the bucket.
This is what happens after your Firebase test mode expires. The console's test mode rules include a date check, something like request.time < timestamp.date(2026, 12, 1). Realtime Database test mode uses a similar now < check. After that day, every request is denied and your app throws permission errors.
The quick fix is to push the date out or swap in true. Both reopen your data to the internet. If you paste the error into an AI tool, check that its answer isn't this edit.
request.auth != null looks safer, but Google lists it as an insecure pattern. Every signed-in user passes, and if sign-up is open, that means everyone.
Firebase security rules before and after
Here's the open version next to an owner-only version for a stories collection. Each document stores its creator's user ID (UID) in author_uid, and the rules compare it with request.auth.uid.
rules_version = '2';
service cloud.firestore {
match /databases/{database}/documents {
match /{document=**} {
// Anyone can read and write everything
allow read, write: if true;
}
}
}rules_version = '2';
service cloud.firestore {
match /databases/{database}/documents {
match /stories/{storyId} {
allow read, delete: if request.auth.uid == resource.data.author_uid;
allow create: if request.auth.uid == request.resource.data.author_uid;
allow update: if request.auth.uid == request.resource.data.author_uid
&& request.auth.uid == resource.data.author_uid;
}
}
}If some data must be public, open only reads (allow read: if true) and keep writes tied to the owner, as in Google's mixed public and private example.
Realtime Database rules need the same treatment. A ".read": true or ".write": true at the top has the same effect as if true. Rules there cascade, so a rule at a parent path overrides stricter rules below it.
{
"rules": {
"some_path": {
"$uid": {
".read": "auth !== null && auth.uid === $uid",
".write": "auth !== null && auth.uid === $uid"
}
}
}
}For Storage, put files under a path that contains the user's UID, compare it with request.auth.uid, and check request.resource.size and request.resource.contentType on writes. The same idea applies to Supabase buckets, covered in Supabase storage policies.
| Rule | Who gets in | Verdict |
|---|---|---|
| allow read, write: if true | Anyone on the internet | Never ship |
| request.auth != null | Anyone who signs up | Only for data every signed-in user may see |
| request.auth.uid == resource.data.author_uid | The document's owner | Good default |
| get(...users/uid).data.admin == true | Users flagged admin | Safe only if clients can't set the flag |
Two traps: rules are not filters, and admin flags
Once your Firestore rules use request.auth.uid, some queries that used to work will fail. Rules are not filters. Firestore checks what a query could return before it runs. If that could include a document you're not allowed to read, the whole request fails, even when every document that exists is yours.
import { collection, getDocs, query, where } from "firebase/firestore"
// Fails: could include other people's stories
await getDocs(collection(db, "stories"))
// Works: the query carries the same constraint as the rule
await getDocs(query(collection(db, "stories"), where("author_uid", "==", user.uid)))The second trap is the admin flag. Google's docs show role-based rules that read a users document with get(), which works only if clients can't write the role field. If users can update their own document freely, anyone can set admin: true on themselves and your admin rule trusts it.
match /users/{userId} {
allow read: if request.auth.uid == userId;
allow create: if request.auth.uid == userId
&& !('admin' in request.resource.data);
allow update: if request.auth.uid == userId
&& !request.resource.data.diff(resource.data).affectedKeys().hasAny(['admin']);
}
match /announcements/{id} {
allow read: if true;
allow write: if get(/databases/$(database)/documents/users/$(request.auth.uid)).data.admin == true;
}Set the flag from server code or the console. Custom claims are another option: small values stored on the user's sign-in token and readable as request.auth.token. Your pages and API routes need the same protection (see exposed admin routes).
How to fix Firebase security rules, step by step
Do this for Firestore, Realtime Database and Storage.
Read what's deployed
Open the Rules tab for each product in the Firebase console. Search for
if true,".read": true,allow read, write;,request.time <(Firestore/Storage test mode) andnow <(Realtime Database test mode).Give every document an owner
Write the creator's UID (
author_uidor similar) when you create the document. Rules need it to decide who owns what.Replace test mode with owner rules
Use
request.auth.uidagainst the owner field, and openreadonly on collections that must be public. Don't touch the date.Lock role and plan fields
Clients must not write
admin,roleorplanon their own documents. Set them from server code or custom claims. Server libraries and the Admin SDK bypass rules entirely, so check the user yourself in those routes (see IDOR in API routes).Test, then deploy from files
Keep rules in files under version control and deploy with
firebase deploy --only firestore,storage,database. Deploying from the CLI overwrites whatever is in the console.
Test it yourself
Run these only against your own project. Start with the emulator, which never touches production.
- Install the tools:
npm i -D @firebase/rules-unit-testing firebase vitest, thenfirebase init emulators. - Add a test that acts as the owner, another user and a signed-out visitor:
import { readFileSync } from "node:fs"
import { afterAll, beforeAll, test } from "vitest"
import { assertFails, assertSucceeds, initializeTestEnvironment, type RulesTestEnvironment } from "@firebase/rules-unit-testing"
import { doc, getDoc, setDoc } from "firebase/firestore"
let env: RulesTestEnvironment
beforeAll(async () => {
env = await initializeTestEnvironment({
projectId: "demo-rules-test",
firestore: { rules: readFileSync("firestore.rules", "utf8") },
})
})
afterAll(() => env.cleanup())
test("owner rules", async () => {
await env.withSecurityRulesDisabled(async (ctx) => {
await setDoc(doc(ctx.firestore(), "stories/s1"), { author_uid: "alice" })
})
const alice = env.authenticatedContext("alice").firestore()
const bob = env.authenticatedContext("bob").firestore()
const anon = env.unauthenticatedContext().firestore()
await assertSucceeds(getDoc(doc(alice, "stories/s1")))
await assertFails(getDoc(doc(bob, "stories/s1")))
await assertFails(getDoc(doc(anon, "stories/s1")))
await assertFails(setDoc(doc(bob, "stories/s1"), { author_uid: "bob" }))
})- Run
firebase emulators:exec --only firestore "npx vitest run".
A good result is a passing run. A bad result is an assertFails line that fails because the request succeeded. That rule is open. Add a similar test that tries to write admin: true on a user's own document. Load the rules file explicitly, as above. If no rules are loaded, the emulator can treat the project as open and your tests pass for the wrong reason.
Then check the deployed project from outside:
# Realtime Database (use your own database name) curl "https://YOUR_DATABASE.firebaseio.com/.json?shallow=true" # Firestore (use your own project ID) curl "https://firestore.googleapis.com/v1/projects/YOUR_PROJECT_ID/databases/(default)/documents/stories?pageSize=1"
If you get JSON with your data back, the rules are open. A permission denied error only proves signed-out access to that collection is blocked. Rely on the emulator tests for signed-in users, and repeat the check for each collection and Storage.
Fix prompt you can paste
Give this to Cursor or Claude Code and read the diff before you deploy.
Audit every Firebase Security Rules file in this repo: firestore.rules, storage.rules, database.rules.json and firebase.json. List with file and line: `allow read, write: if true`, `allow read, write;` in Storage, ".read": true / ".write": true in Realtime Database, rules whose only condition is request.auth != null, `request.time <` or `now <` date checks left over from test mode, and any rule that lets a client write admin, role, isAdmin or plan fields on their own user document. Replace them with owner-based rules using request.auth.uid and the owner field the code actually writes. Keep public read only on collections that must be public. Do NOT extend any date. Gate admin access with custom claims or a users document clients cannot write. Also find client queries (getDocs on a whole collection) that don't carry the constraint the rule requires, and fix the query, not the rule. Add Vitest tests with @firebase/rules-unit-testing: owner succeeds, other user fails, signed-out fails, user cannot set their own admin flag. Before editing, output a table of every finding with file, line number and why it is open. After editing, list each changed file and line range with the old and new rule, explain each change in plain words, and tell me which rules to deploy.
If you'd like a second check on your repo afterwards, Leakcade can scan it.
Further reading
- Firebase docs: Avoid insecure rules
- Firebase docs: Get started with Cloud Firestore Security Rules
- Firebase docs: Securely query data (rules are not filters)
- Firebase docs: Build unit tests for Security Rules
FAQ
My Firebase test mode expired. What should I do?
Replace the date-based rule with owner rules that require sign-in and compare request.auth.uid to an owner field. Don't push the date out or switch to if true, because both leave your data open.
Is 'allow read, write: if true' ever okay?
Not for writes. Google's docs say never to use it in production because it lets anyone overwrite your entire database. If a collection must be public, open only allow read: if true and tie writes to the owner.
Why does my Firestore query fail after I added request.auth.uid rules?
Rules are not filters. Firestore checks whether the query could return documents you can't read, and fails the whole request if so. Add the same constraint to the query, for example where("author_uid", "==", user.uid).
Is it safe that my Firebase config is visible in the browser?
The web config only identifies your project and grants no access by itself. Your security rules decide who can read and write, so with open rules anyone who copies the config can read your data. Keep real secrets such as service account keys out of client code.
Do Firebase security rules apply to the Admin SDK?
No. Server client libraries and the Admin SDK bypass Firestore rules and are governed by Google Cloud IAM (account permissions) instead. Any API route that uses them needs its own check of who the caller is and what they may touch.
Want a second pair of eyes?
We review your whole repo and write a fix prompt for every finding. $19, report within an hour.
Check my repoLeakcade is an automated check. It can miss vulnerabilities and is not a substitute for a professional security audit or pentest. Provided as is. You stay responsible for your code. Where the law allows, our liability is limited to what you paid ($19). Terms