DenyFirst blog
The one Firebase rule that leaks everyone's data
There is a line of code that shows up in a startling number of apps built with Lovable, Bolt, Firebase Studio, and their cousins. It looks like this:
match /posts/{id} {
allow read: if request.auth != null;
}
It *feels* safe. There's a condition. There's the word auth. If you're skimming, it reads as "you have to be logged in to see this," and being logged in feels like a lock. It isn't. It's the single most common serious vulnerability of this software generation, and it means exactly one thing:
Any person who can create an account can read every record in that collection — including records that belong to other users.
Authentication is not authorization
request.auth != null answers one question: *is the caller signed in?* It says nothing about *which* data the caller is allowed to see. Those are two different questions, and the gap between them is where the breach lives.
Signing up for most of these apps is free and instant. So "you must be logged in" is not a barrier — it's a formality. An attacker registers throwaway@example.com, gets a valid request.auth, and now satisfies the rule for *every* document in the collection. Not their documents. All of them.
The rule you actually wanted scopes each read to the owner:
match /posts/{id} {
allow read: if request.auth != null
&& resource.data.ownerId == request.auth.uid;
}
Now being logged in is necessary but not sufficient: the record also has to belong to you.
What this looks like in the wild
This isn't hypothetical. We've reproduced it against real apps whose Firestore rules were effectively allow read: if request.auth != null — where a single free signup was enough to read years of other users' records across an entire collection. No exploit chain, no clever payload — just a free signup and a normal read. The app worked perfectly. The rules were the problem.
The reason it survives code review is that the app behaves correctly for the person testing it. You log in as yourself, you see your data, everything looks scoped — because the *UI* only ever asks for your records. The rule that governs the *database* is far more permissive than the screens suggest, and nobody notices until someone reads the collection directly.
How to check your own app
You can test this without any special tooling. With the app's public Firebase config (it ships in the client bundle — it's meant to), a signed-in request to a collection either comes back with data that isn't yours, or it comes back denied. The honest test is: create a brand-new account that owns nothing, then try to read a collection. If records come back, those records belong to someone else.
The failure modes to look for:
allow read: if request.auth != null— logged-in users can read everything.allow read: if true— the internet can read everything.- Rules on the parent that a subcollection quietly inherits or overrides.
- Storage buckets with
allow readthat hand out every uploaded file.
The fix is a habit, not a patch
Every read and write rule should answer *"is this specific record theirs?"*, not *"are they anybody?"*. Filter by the authenticated user or tenant id. For item reads, verify the document's owner field. Treat request.auth != null as the beginning of a rule, never the whole of one.
DenyFirst proves this class of bug against your own backend — read-only, from an account that owns nothing, so anything it reads back is by definition someone else's. If nothing opens, you pay nothing. See if we can get in →
← All posts