A Firebase security rules misconfiguration is a Firestore collection, a Realtime Database path, or a Storage bucket that any visitor can read or write, because the Security Rules protecting it were left open or written to check the wrong thing. Firebase client SDKs talk to the database straight from the browser, and the Firebase config that authorises those calls ships in your page source where anyone can copy it. Security Rules are the only access control standing between a stranger and every record you hold.
What do Firebase Security Rules actually do?
Security Rules run on Google’s servers, in front of the database, below your application code. When a request arrives, Firebase finds the rule that matches the path being touched and evaluates it. If no rule allows the operation, it is denied. So the safe starting state is a database that answers nothing, and you grant access back one rule at a time.
Take an invented app, Acme Journal. Each user writes private entries, and every document carries an ownerId field. A correct rule reads an entry only if the signed in caller owns it.
rules_version = '2';
service cloud.firestore {
match /databases/{database}/documents {
match /entries/{entryId} {
allow read, write: if request.auth != null
&& request.auth.uid == resource.data.ownerId;
}
}
}
The load bearing part is request.auth.uid. Firebase verifies the caller’s ID token and hands the rule the verified user id, and the rule compares it against the ownerId already stored on the document. A caller with no session has no request.auth, so the check fails and the read returns nothing. Access control is now a property of the data, not of the screen that renders it.
Why is the Firebase config not a secret?
The Firebase config block, the one with apiKey and projectId, is meant to be public. It is not a credential. It only names your project so the client SDK knows which backend to call, and Google’s own docs say it is fine to ship in client code. Anyone can read it out of your bundle and send requests with it. That is expected, as long as every collection, path, and bucket it reaches has rules deciding what an anonymous or other caller may see. The config identifies the project. It does not authorise anything on its own.
The Firebase config is not a vulnerability. A collection that answers that config with everyone’s documents is.
What are the failure shapes of a Firebase security rules misconfiguration?
Nearly every real case is one of four shapes, and all of them end in the same place: a caller reads or writes documents that are not theirs.
- Test mode left on. New projects offer a starter ruleset that allows all reads and writes, sometimes until a fixed date. It is meant for a demo afternoon and then forgotten, so the database sits open on the internet.
- Signed in mistaken for authorized. A rule checks
request.auth != nulland stops there. Every logged in user of the app now reads every other user’s documents, because the rule confirms identity but never checks ownership. - A new collection with no rule of its own. A feature ships a
paymentsorinvitescollection, and nobody added a matching block. Depending on how the rules are written, the collection falls through to a broad parent match and inherits access it should never have. - Storage world readable. The Storage rules were opened for a file upload feature and never tightened, so uploaded receipts and profile images are fetchable by anyone with the URL pattern.
The first shape is the one people ship without meaning to. It looks like this, and it is one deploy away from production:
rules_version = '2';
service cloud.firestore {
match /databases/{database}/documents {
// test mode: open to the world, sometimes until a date
match /{document=**} {
allow read, write: if true;
// or the timed variant a quick start hands you:
// allow read, write: if request.time
// < timestamp.date(2025, 1, 1);
}
}
}
This is common rather than rare. AI app builders and quick start templates routinely generate permissive rules to get a demo working fast, and public scans of quickly built apps have repeatedly found permissive database rules left in place. We cite that as a pattern, not a headcount. The point is the shape of the mistake: fast assembly puts a database on the internet, and locking the rules is the step that gets deferred. The same pressure produces the sibling problem in a Supabase RLS misconfiguration, where a public key reaches tables that Row Level Security never fenced off.
Why is auth != null not authorization?
Because it answers a different question. request.auth != null means the caller signed in to your Firebase project. It says nothing about which documents belong to them. If Acme Journal has ten thousand users and its entries rule stops at that check, any one account can list the whole entries collection and read everyone else’s private writing. Authentication is who you are. Authorization is what you are allowed to touch, and the rule has to compare the verified request.auth.uid against the owner field on the specific document.
How do you verify it from outside instead of trusting the console?
Ask the database the way a stranger would, using the public config and the documented REST endpoint, with no SDK in the way. Firestore exposes a plain REST API for every project.
curl "https://firestore.googleapis.com/v1/projects/ACME_PROJECT/databases/(default)/documents/entries"
A permission denied error means the rules held. Documents coming back mean a stranger reads that collection. Then repeat from three more seats, each catching a different failure:
- Signed in as a real user, asking for another user’s documents. Get an ID token for a throwaway account and read a document whose
ownerIdis someone else. It should be denied. - Write, not just read. Attempt a create and an update. Read and write are separate clauses, so testing reads alone leaves half the rule untested.
- Storage and every new collection. Check bucket objects by their URL pattern, and enumerate collections rather than testing only the ones you remember. The risky one is usually the collection added last.
How do you fix it?
- Deny by default. Start from rules that allow nothing and grant access one match block at a time. Never rely on a broad
match /{document=**}with anallowin it. - Check ownership, not just presence. Compare
request.auth.uidagainst a stored owner field the user cannot set, on writes as well as reads. Treatallow read, write: if trueandif request.auth != nullalone as findings. - Give every collection its own rule. When a feature adds a collection, add its match block in the same change, so nothing falls through to a permissive parent.
- Lock Storage the same way. Scope object reads and writes to the owner, and never leave a bucket world readable after a file feature ships.
- Test in the simulator and in CI. Run the rules simulator for the unauthenticated and other tenant cases, then encode the same assertions with the emulator so a future deploy cannot quietly reopen a path.
Why do scanners miss this?
Because nothing here is malformed. The request is well formed, the config is valid, the endpoint is documented, and the response is a clean 200. This is broken access control, the same class as broken object level authorization. A scanner can tell you a URL responded. It cannot tell you the documents in that response belonged to someone else, because who is allowed to see what is a fact about this application and nothing else. The same reasoning gap runs across the patterns in our guide to securing quickly built apps, and more of our writing on it sits under access control.
Answering it takes a tester that learns the app’s own rules about ownership, forms an idea about where the rules do not enforce them, and proves it by fetching a document it should never have been given. As an early and encouraging signal, a frontier model drove the full methodology on its own and identified and verified real access control and injection issues in test applications it had not seen before. That is exactly the kind of assumption an autonomous security researcher, built to test assumptions rather than match payloads, is meant to probe, and it is what UnboundCompute is being built to do. You can read more on our about page.
Frequently asked questions
What is a Firebase security rules misconfiguration?
It is a Firestore collection, Realtime Database path, or Storage bucket left open or protected by a rule that checks the wrong thing. Because Firebase client SDKs talk to the database straight from the browser, anyone holding the public Firebase config can then read, and often write, data that is not theirs.
Is it safe to put the Firebase config and apiKey in the browser?
Yes, the Firebase config is meant to be public and only names your project, so it is not a secret. It is safe only when every collection, path, and bucket it can reach has Security Rules that decide what a given caller may see. The config identifies the backend, it does not authorise anything on its own.
Why is request.auth != null not enough in a Firestore rule?
Because it confirms the caller signed in but never checks which documents belong to them. Any logged in user can then read every other user’s data. A correct rule compares the verified request.auth.uid against a stored owner field on the specific document.
How do I test whether my Firebase Security Rules are working?
Query the Firestore REST endpoint from outside with no session and confirm you get permission denied. Then repeat as a signed in user asking for another user’s documents, test writes as well as reads, and check Storage buckets and any newly added collection. The rules simulator and the local emulator let you assert the same cases in CI.
What is test mode in Firebase and why is it risky?
Test mode is a starter ruleset that allows all reads and writes, sometimes until a fixed expiry date, so a new project works instantly during a demo. It is risky when it reaches production, because the database is then open to anyone on the internet. Replace it with rules that deny by default before you ship.
Is a Firebase security rules misconfiguration the same as a Supabase RLS problem?
They are the same shape of bug on different platforms. In both cases the client SDK reaches the database directly from the browser with a public key, and a single layer of access control, Security Rules or Row Level Security, is the only thing keeping callers to their own data. When that layer is open or checks the wrong value, strangers read rows or documents that are not theirs.
Put an autonomous researcher on your own systems
UnboundCompute is an autonomous security researcher that reasons about how an application fits together and proves the access control and injection bugs it finds. We are opening a small number of founding design partner seats: private early access pointed at a staging target you choose, and a say in what it looks for. If your team ships software worth pressure testing, apply to the design partner program.
