FREE1 free audit every month. No card, every finding included.

GUIDESEPISODES

EP 13 · GHOSTAster × Claude

Firebase test mode left open? Lock your rules

You picked “test mode” on day one. Thirty days later the app broke, so you set the rules to let everyone in. Now anyone with your project ID can read every user’s data. Here are the three rules that lock it, and the two-minute test that proves a stranger gets “denied”.

  • 9 min read
  • Beginner
  • Oct 16, 2026
Voxel episode cover tagged EP. 13 on a plum-violet night background with faint code: the headline reads TEST MODE? in cream and LOCK IT. in lime; in a moonlit Victorian hallway, Aster, scared, points at an old oak file cabinet whose drawers have slid open, his closed silver laptop at his side and the lime lock on his black shirt visible; Claude, an orange clay block with two black eye slits, aims a night-vision camcorder with a red light at it, while a hooded figure with red eye glints reads a paper card behind the cabinet.

01THE HAUNTING

The drawers that open by themselves

Why it mattersThe lock was never on. The 30-day timer was the only thing that would have closed it.

Here’s how it happens. You start a Firebase project and the console asks you to pick a mode. You pick test mode, because you just want the app to work.

Illustration: in a moonlit Victorian hallway with wine-red wallpaper, an old oak file cabinet with a brass label reading USERS has drawers sliding open by themselves while paper cards float out; a figure in a navy hooded cloak with red eye glints reads one card behind it; Aster stands on the left, scared, his closed silver laptop at his side and the lime lock on his black shirt visible; Claude, an orange clay block with two black eye slits, films the cabinet with a night-vision camcorder.
Nobody touched the USERS drawer. Somebody is reading it anyway.
Open the full chapterIncludes a note

Thirty days later every request fails. You search the error, find a fix that makes it go away, and paste it in. The app works again.

That fix was a new expiry date, or allow read, write: if true;. Now your database has no lock at all. Anyone who finds your project ID can read your users, change them, or delete them.

02THE SPIRIT BOX

What “test mode” actually means

Why it mattersTest mode is a 30-day pass for everyone. Extending it extends the pass.

When you create a database or storage instance, Firebase asks you to choose. In its own words, you choose whether your rules “restrict access to your data (Locked mode) or allow anyone access (Test mode).”

Illustration: in a candlelit Victorian parlour, Claude, the orange clay block, holds up a small vintage spirit box radio with a glowing amber display, and above it glowing pumpkin-orange letters float in the air spelling TEST MODE; Aster leans in beside him, nervous, his closed silver laptop at his side and the lime lock on his shirt visible.
The spirit box only knows two words.
Open the full chapterIncludes code

The Firestore quickstart says test mode is “good for getting started” but “allows anyone to read and overwrite your data.”

The test-mode rule, word for word

This is the template the Firebase CLI writes for you. The comment is Google’s, not ours:

firestore.rules (test mode)
rules_version='2'

service cloud.firestore {
  match /databases/{database}/documents {
    match /{document=**} {
      // This rule allows anyone with your database reference to view, edit,
      // and delete all data in your database. It is useful for getting
      // started, but it is configured to expire after 30 days because it
      // leaves your app open to attackers. At that time, all client
      // requests to your database will be denied.
      //
      // Make sure to write security rules for your app before that time, or
      // else all client requests to your database will be denied until you
      // update your rules.
      allow read, write: if request.time < timestamp.date(2026, 11, 15);
    }
  }
}

The CLI fills in a real date 30 days out. Until that date, anyone can read, edit and delete everything.

The mistake is what happens on day 30

When the date passes, every request from your app is denied. That’s the safe part. The danger is the quick fix: push the date out a year, or swap the condition for if true. Both keep the door open, and the second one never closes.

✕ Don’tfirestore.rules
// Allow read/write access to all users under any conditions
// Warning: **NEVER** use this ruleset in production; it allows
// anyone to overwrite your entire database.
service cloud.firestore {
  match /databases/{database}/documents {
    match /{document=**} {
      allow read, write: if true;
    }
  }
}

Firebase’s own warning, copied from its docs: never use this in production.

✓ Dofirestore.rules
service cloud.firestore {
  match /databases/{database}/documents {
    // Make sure the uid of the requesting user matches name of the user
    // document. The wildcard expression {userId} makes the userId variable
    // available in rules.
    match /users/{userId} {
      allow read, update, delete: if request.auth != null && request.auth.uid == userId;
      allow create: if request.auth != null;
    }
  }
}

Firebase’s own owner-only sample: each user reads and changes only their own document.

03THE UNMASKING

Who is the ghost? Anyone.

Why it mattersThe stranger isn’t clever. The rule just says “anyone”.

Open rules don’t need a hacker. Firebase’s page on insecure rules says it plainly: “You might think you’re the only person using your app, but if you’ve deployed it, it’s available on the internet.”

Illustration: in a candlelit Victorian room, Aster pulls back the navy hood of the cloaked stranger and under it there is no face, only a plain white wooden sign on a stick reading ANYONE; Aster looks shocked, his closed silver laptop in his other hand and the lime lock on his shirt visible; Claude, the orange clay block, films the sign with his camcorder.
Under the hood: nobody special. That’s the point.
Open the full chapterIncludes a table, a note

It goes on: if you’re not authenticating users and configuring security rules, “anyone who guesses your project ID can steal, modify, or delete the data.”

This happened to Tea

Tea, a dating-safety app for women, was breached in July 2025. Its own statement blamed a “legacy data storage system”. The leaked dataset held “approximately 72,000 images, including approximately 13,000 selfies and photo identification submitted by users during account verification”.

According to 404 Media, which broke the story, 4chan users said they had found an exposed database on Firebase. Days later, TechCrunch reported a second exposure: over 1.1 million private messages.

What leakedHow manySource
Selfies and photo IDs from account checksAbout 13,000Tea’s statement
Images from posts, comments and DMsAbout 59,000Tea’s statement
Private messagesOver 1.1 millionTechCrunch

04RULES 1 + 2

Signed in first, then owner only

Why it mattersSigned in tells you who they are. Owner only decides what they can open.

The first rule is easy: only signed-in users get in. That alone stops strangers. But it doesn’t stop your other users.

Illustration: in a candlelit Victorian room, every drawer of a tall oak cabinet is shut with its own glowing orange keyhole; Aster turns a brass key in one drawer, his own, which slides open with a single card inside; Claude, the orange clay block, holds a small ring of brass keys beside him; Aster’s silver laptop sits open on an oak table; the hooded stranger lurks in the dark background.
One key per drawer. Yours opens yours.
Open the full chapterIncludes code

Why auth != null alone is not enough

Firebase warns about this exact rule. If a rule includes auth != null, it says to “confirm that you want any logged-in user to have access to the data”. Anyone can sign up for your app. Then they can read every record.

✕ Don’tfirestore.rules
// Not recommended: any logged-in user can read and write everything
match /{document=**} {
  allow read, write: if request.auth != null;
}

Firebase’s verdict on this pattern: “Not recommended: Any logged-in user has read and write access to your entire database.”

The owner-only rule (Cloud Firestore)

Store each user’s data at users/{their uid}, then check the ID on every request. This is the sample from Firebase’s docs, exactly:

✓ Dofirestore.rules
service cloud.firestore {
  match /databases/{database}/documents {
    // Make sure the uid of the requesting user matches name of the user
    // document. The wildcard expression {userId} makes the userId variable
    // available in rules.
    match /users/{userId} {
      allow read, update, delete: if request.auth != null && request.auth.uid == userId;
      allow create: if request.auth != null;
    }
  }
}

If your documents live in a shared collection instead, save the owner’s ID in each one and check that field:

✓ Dofirestore.rules
service cloud.firestore {
  match /databases/{database}/documents {
    // Allow only authenticated content owners access
    match /some_collection/{document} {
      // Allow reads and deletion if the current user owns the existing document
      allow read, delete: if request.auth.uid == resource.data.author_uid;
      // Allow creation if the current user owns the new document
      allow create: if request.auth.uid == request.resource.data.author_uid;
      // Allow updates by the owner, and prevent change of ownership
      allow update: if request.auth.uid == request.resource.data.author_uid
                    && request.auth.uid == resource.data.author_uid;
    }
  }
}

The update rule checks both the old and new owner, so nobody can hand a document to themselves.

The same lock for Realtime Database

✕ Don’tdatabase.rules.json
{
  // Allow read/write access to all users under any conditions
  // Warning: **NEVER** use this ruleset in production; it allows
  // anyone to overwrite your entire database.
  "rules": {
    ".read": true,
    ".write": true
  }
}

Open to everyone, forever.

✓ Dodatabase.rules.json
{
  "rules": {
    "some_path": {
      "$uid": {
        // Allow only authenticated content owners access to their data
        ".read": "auth !== null && auth.uid === $uid",
        ".write": "auth !== null && auth.uid === $uid"
      }
    }
  }
}

Each signed-in user reads and writes only under their own ID.

05RULE 3

Lock the attic too: Cloud Storage rules

Why it mattersThree rules files, three locks. Missing one leaves a window open.

Profile pictures, uploads and ID photos usually live in Cloud Storage. It has its own storage.rules, separate from your database rules. An app can have a locked database and an open bucket.

Illustration: in a moonlit attic under sloped oak beams full of framed photos and polaroids on strings, Aster closes the lid of an old trunk and Claude, the orange clay block, snaps a big glowing orange padlock shut on it; Aster holds his closed silver laptop at his side and the lime lock on his shirt is visible; a round window shows a full moon.
The photos were never in the database. They were in the attic.
Open the full chapterIncludes code, a note
✕ Don’tstorage.rules
// Anyone can read or write to the bucket, even non-users of your app.
// Because it is shared with App Engine, this will also make
// files uploaded using App Engine public.
// Warning: This rule makes every file in your Cloud Storage bucket accessible to any user.
// Apply caution before using it in production, since it means anyone
// can overwrite all your files.
service firebase.storage {
  match /b/{bucket}/o {
    match /{allPaths=**} {
      allow read, write;
    }
  }
}

Firebase: “This rule makes every file in your Cloud Storage bucket accessible to any user.”

✓ Dostorage.rules
// Grants a user access to a node matching their user ID
service firebase.storage {
  match /b/{bucket}/o {
    // Files look like: "user/<UID>/file.txt"
    match /user/{userId}/{fileName} {
      allow read, write: if request.auth.uid == userId;
    }
  }
}

Save each user’s files under user/<their uid>/. Only they can read or write them.

firebase.json
{
  "firestore": { "rules": "firestore.rules" },
  "database":  { "rules": "database.rules.json" },
  "storage":   { "rules": "storage.rules" }
}
terminal
firebase deploy --only firestore:rules,storage,database

Deploys only your rules. Run it after the Playground test below passes.

06THE TEST

Prove it: the Rules Playground says “denied”

Why it mattersA rule you never tested is a guess. Denied is the answer you want.

Don’t trust a rule because it looks right. Test it the way the stranger would come in. Firebase’s docs: “To quickly test your updated Firebase Security Rules in the Firebase console, use the Rules Playground.”

Illustration: Claude, the orange clay block, points his night-vision camcorder at the open front door of a Victorian house while a burst of orange light blows the hooded stranger backwards out into the moonlit night; a red stamp-style banner in the air reads DENIED; Aster stands beside Claude pumping one fist, his closed silver laptop at his side and the lime lock on his shirt visible.
Out you go.
Open the full chapterIncludes steps, a table, a note
  1. Open the Firebase console, go to Firestore Database → Rules (or Realtime Database or Storage), and open the Rules Playground.
  2. Pick get as the simulation type. For Location, enter someone else’s record, like users/some-other-user-id.
  3. Set Authentication type to unauthenticated. Click Run.
  4. The banner above the editor must say the request was denied. If it says allowed, your door is open.
  5. Now test as a signed-in user with a specific ID. Reading their own users/<that id> should be allowed. Reading someone else’s should be denied.
WhoReadsExpected
UnauthenticatedAny user’s recordDenied
Signed in as user Ausers/AAllowed
Signed in as user Ausers/BDenied
UnauthenticatedA file in user/B/Denied

07CASE CLOSED

Your before-launch rules check

Why it mattersFive minutes with the Playground beats a headline about your app.

Paste the prompt at the top of this page into Claude Code, Cursor or any coding agent. It finds your rules files, lists every open rule with its file and line, and suggests the owner-only version. It changes nothing until you say so.

Illustration: at dawn, soft gold light pours into a calm Victorian living room; Aster and Claude, the orange clay block, sit together on an old red sofa, happy and tired, Aster with his silver laptop open on his knees and the lime lock on his shirt visible, a camcorder resting beside Claude; on an oak easel a cream card shows three lime check marks.
Morning. The house is quiet again.
Open the full chapterIncludes a table, a note
CheckBad signGood sign
Firestore rulesif true or request.time < timestamp.date(...)request.auth.uid == userId
Realtime Database rules".read": true or ".write": trueauth.uid === $uid
Cloud Storage rulesallow read, write; on {allPaths=**}Files under user/{userId}/ checked against request.auth.uid
Signed-in checkauth != null alone on personal dataSigned in AND owner only
Where rules liveOnly in the consoleIn the repo, listed in firebase.json
ProofIt looks rightRules Playground: unauthenticated read is denied

THE QUICK READ

What to take away.
In one minute.

  1. 01

    Firebase’s own docs say test mode “allows anyone to read and overwrite your data.”

  2. 02

    The test-mode rule expires after 30 days. The mistake is what comes next: a new date, or if true, so the app works again.

  3. 03

    Your project ID ships inside your app. Firebase says “anyone who guesses your project ID can steal, modify, or delete the data.”

  4. 04

    Signed in is not enough. With request.auth != null alone, every user can read every other user’s records.

  5. 05

    Owner-only rules check the user’s ID: request.auth.uid == userId. Cloud Storage needs its own rules too.

  6. 06

    Test it in the Rules Playground as an unauthenticated user. A read of someone else’s record must say denied.

THE FIX, IN ONE PASTE

Paste this. Find every open door in your rules.

Paste this into Claude Code, Cursor, Codex, Gemini CLI or Copilot. It only reads and reports; it won’t change your code until you approve.

agent-prompt.md
You are a security reviewer for this codebase. Investigate ONE issue: Firebase Security Rules that let anyone, or any signed-in user, read or change other people's data.

Where to look:
- `firebase.json` (it names the rules files), then `firestore.rules`, `database.rules.json`, `storage.rules` and any other `*.rules` file.
- Every collection, path and Storage folder the app really uses: `collection(`, `doc(`, `ref(`, `uploadBytes(`.

How to judge each rule:
- Critical: `if true`, `allow read, write;` with no condition, `".read": true` or `".write": true`.
- Critical: test-mode rules, `request.time < timestamp.date(...)`, even if the date is in the future. Say when it expires.
- High: `request.auth != null` (or `auth != null`) alone on personal data. Any signed-in user can read every record.
- OK: owner-only rules, like `request.auth.uid == userId` on `/users/{userId}`, `auth.uid === $uid`, or `request.auth.uid == resource.data.author_uid`.
- Also flag Storage paths that are not scoped to the user's ID, and say if no rules files are in the repo (then the live rules exist only in the Firebase console).

Report a table: file:line | what's wrong | severity (critical, high, medium, low) | the minimal owner-only rule.

Then explain how to check each fix in the Firebase console's Rules Playground: Authentication type unauthenticated, a path to someone else's record, Run, expect denied.

Rules: don't modify any file or deploy rules until I approve. Never print keys, config values or user data: mask them.

Then ask me which fixes to apply.

Paste it into the agent you already build with

  • Claude
  • ChatGPT
  • Gemini
  • Cursor
  • Copilot

QUESTIONS PEOPLE ASK

Good questions.
Short answers.

What is Firebase test mode?

It’s a starting rule set you can pick when you create a database or bucket. Firebase’s quickstart says it is good for getting started but “allows anyone to read and overwrite your data.” The Firebase CLI version expires after 30 days.

What happens when Firebase test mode expires?

All client requests to your database are denied until you update your rules. Don’t fix it by pushing the date out or switching to if true. Write owner-only rules instead.

Is allow read, write: if true ever OK?

Not in production. Firebase’s own sample carries the warning “NEVER use this ruleset in production; it allows anyone to overwrite your entire database.”

Is request.auth != null enough?

No, not for personal data. It only checks that someone is signed in. Anyone can create an account, so every user could read every other user’s records. Add an owner check like request.auth.uid == userId.

Isn’t my Firebase config a secret?

No. The config and project ID ship inside your app, so treat them as public. Your security rules are what protect the data.

How do I test my Firebase rules?

Use the Rules Playground in the Firebase console: set Authentication type to unauthenticated, enter someone else’s record as the location, and click Run. It must be denied. For automated tests, use the Firebase Emulators.

Do I need separate rules for Cloud Storage?

Yes. Cloud Storage has its own storage.rules. Locking Firestore or Realtime Database doesn’t protect uploaded photos and files.

Does Aster check my Firebase rules?

Aster reads your repository. Its scan flags Firestore, Realtime Database and Storage rules files that are open, in test mode, or open to any signed-in user. It can’t see rules you only published in the Firebase console, and it doesn’t prove one user can’t read another’s data. The Rules Playground test above covers that.

Done with the guide?
Let Aster check the rest.

1 free audit every month. Aster reads your code, env files, database rules and packages. You get each finding in plain English, with the file and line.

Aster

CHECKING

YOUR GUIDE TO ASTER

Hi, I’m Aster.
Ask me what Aster checks.

I can’t see your code from here. Please don’t paste secrets or private code.

Prepared answers are always available.