Home/Journal/Pattern
Patternfirebasecloud-storageaccess-control

The Cloud Database With the Door Left Open

Managed databases and buckets leak data when rules check that a caller is logged in but never that they own the record. Here is the pattern and the fix.

Rahul Dharan··8 min read

What this is

An exposed cloud database is a managed data store - Firebase, Firestore, an S3 or GCS bucket - whose access rules let a caller read data they have no right to. The store is not misconfigured in some exotic way. In almost every case the rules check one thing correctly and skip the thing that matters: they confirm the caller is authenticated, and never confirm the caller owns the record. Authentication is checked. Authorization is not.

That single missing check is the difference between a store that serves one user their own data and a store that serves any user everyone’s data. It is one of the most common critical findings we report, and it rarely looks like a bug in the code. It looks like a rule that reads as reasonable until you ask it the second question.

Why it keeps happening

Managed data stores moved the security boundary out of the application and into a rules file, and the rules file is easy to get half right. A developer writes a rule that says “only logged-in users can read this collection,” ships it, and moves on. It feels like a lock. It is a lock on the building’s front door with no lock on any of the individual apartments inside.

The pressure that produces this is ordinary. A team wiring up Firebase or a bucket wants the app to work, and the fastest rule that makes the app work while keeping out anonymous strangers is request.auth != null. It passes review because reviewers read it as “you must be authenticated,” which is true, and stop there. The question that catches it - “authenticated as whom, and does that person own this row?” - is exactly the question that gets skipped when the goal is to ship the feature.

The same shape appears in storage buckets. A bucket starts public because that was convenient during development, or directory listing gets left on because someone needed to browse files once, and the temporary state becomes the shipped state. And there is a subtler trap: a bucket can be gated by two independent systems - cloud IAM and the storage service’s own rules - and a team that locks one can reasonably believe the data is protected while the other stands wide open over the same bytes.

How the attack works

The mechanism is the same across every variant, and it is unglamorous:

  1. Find the store. The app’s own client code tells you where its data lives - a Firebase project ID, a bucket name, an API host. Black-box, from the browser, this is visible.
  2. Register or connect. Where the rule only checks auth-presence, the attacker signs up for a normal account through the app’s own front door. Where the bucket is public or listing is on, even that is unnecessary.
  3. Read past your own row. With a valid session, the attacker queries the collection without the filter the app’s UI would normally apply, or lists the bucket root, or reads a sibling document by ID. The rule sees an authenticated caller, returns 200, and hands back data belonging to other tenants.

There is no exploit chain here, no memory corruption, no clever payload. The store does exactly what its rules permit. The attacker simply asks for more than the UI ever asks for, and the rules say yes.

What it looks like in the wild

We have found this across sectors that have nothing in common except the technology underneath, which is what makes it a pattern.

The consistent theme: the data was never behind a broken lock. It was behind a lock that checked the wrong thing, or no lock the team remembered was there.

How to tell if you are exposed

A short self-check any team can run:

  1. Read your own rules for the phrase request.auth != null (or its equivalent). Every place it appears without a comparison to the record’s owner or tenant is a place where any authenticated user reads everyone’s data.
  2. From an account you registered yourself, query a collection without the filter your UI applies. If you get back rows that belong to other users, the store is authorizing on login, not ownership.
  3. List your buckets and check two things separately: is the bucket public, and is directory listing on? Then check the storage-service rules and the cloud IAM policy independently - a lock on one is not a lock on the other.
  4. Point a public-exposure scan at your own project IDs and bucket names. If an anonymous client can read anything, assume someone already has.

How to fix it

The takeaway

Authentication asks who you are; authorization asks what you are allowed to touch. An exposed cloud database is almost always a store that answered the first question and skipped the second. If your rules confirm the caller is logged in but not that the caller owns the record, every account is a master key. This teardown pattern is covered in depth across The Mental-Health Records Anyone Could Read and its siblings.

Frequently asked

What is an exposed cloud database?
It is a managed database or storage bucket (Firebase, Firestore, S3, GCS) whose access rules let a caller read or list data they should not see. Usually the rules confirm the caller is authenticated but never check that the caller owns the specific record, so any signed-up user can read everyone's data.
Why is checking request.auth != null not enough?
request.auth != null only proves the caller is logged in. It does not prove the caller owns the record they are reading. On a multi-tenant app, every authenticated user passes that check, so anyone who can register an account can read every tenant's data. The rule must compare the caller's identity to the record's owner.
How do you secure a Firebase or S3 bucket?
Default every bucket and collection to private, write rules that match the caller's identity against the record owner or tenant, disable directory listing, and check the storage-rules layer separately from cloud IAM because they are two different gates over the same data. Scan your public surface routinely.

This is one finding from a harness that runs continuously. See how Greywatch finds, proves, and fixes them.

How it works →