DEV Community

Frank Gilroy
Frank Gilroy

Posted on Originally published at beingrealfrank.github.io

The Firebase security rules checklist for AI-built apps

Your Firebase web config, apiKey included, is public by design: it ships in your JavaScript and anyone can read it. The only thing between that config and your users' data is your security rules, and AI tools often leave them in test mode or loosen them until the app "works". Here's how to check them in about ten minutes.

1. Find test-mode and allow-all rules

From your project folder:

grep -nE "if true|request.time <|\"\.(read|write)\": *true" firestore.rules storage.rules database.rules.json
Enter fullscreen mode Exit fullscreen mode

Any hit is an open door. allow read, write: if request.time < timestamp.date(...) is the default test-mode rule: everything is public until that date, and when it passes the app breaks and someone "fixes" it with if true.

2. "Signed in" is not "allowed"

allow read, write: if request.auth != null; lets every user who signs up read and change every other user's documents. Tie each document to its owner:

match /users/{userId}/{document=**} {
  allow read, write: if request.auth != null && request.auth.uid == userId;
}
Enter fullscreen mode Exit fullscreen mode

Where the owner is a field, check it on both sides: resource.data.ownerId == request.auth.uid for reads and updates, and request.resource.data.ownerId == request.auth.uid for creates.

3. Lock the fields users must not change

If users can update their own profile document, they can also set role: "admin" or credits: 999999 on it unless your rules forbid it:

allow update: if request.auth.uid == userId
  && !request.resource.data.diff(resource.data).affectedKeys().hasAny(['role', 'plan', 'credits']);
Enter fullscreen mode Exit fullscreen mode

4. Check the Realtime Database too

If you use it, database.rules.json with ".read": true or ".write": true at the root opens the whole database. Scope it per user:

{
  "rules": {
    "users": {
      "$uid": { ".read": "$uid === auth.uid", ".write": "$uid === auth.uid" }
    }
  }
}
Enter fullscreen mode Exit fullscreen mode

5. Check Storage rules

Storage has its own rules file, and AI tools often leave it wide open. Scope uploads to the user's own folder and limit size and type:

match /b/{bucket}/o {
  match /uploads/{userId}/{fileName} {
    allow read: if request.auth != null && request.auth.uid == userId;
    allow write: if request.auth != null && request.auth.uid == userId
      && request.resource.size < 5 * 1024 * 1024
      && request.resource.contentType.matches('image/.*');
  }
}
Enter fullscreen mode Exit fullscreen mode

6. Keep admin credentials out of the browser and the repo

The web config is fine to ship. A service-account key (a JSON file with "type": "service_account" and a private_key) is not: it bypasses every rule. It belongs only on a server or in Cloud Functions. Check your history too:

git log --all --oneline -S '"private_key"'
Enter fullscreen mode Exit fullscreen mode

If that prints anything, delete the key in the Google Cloud console (IAM → Service accounts → Keys) and create a new one. Removing the file from the repo is not enough.

7. Prove it from the outside

Signed out, a private collection must refuse to list:

curl "https://firestore.googleapis.com/v1/projects/<project-id>/databases/(default)/documents/<collection>?pageSize=5"
Enter fullscreen mode Exit fullscreen mode

You want 403 PERMISSION_DENIED. If you get documents back, anyone on the internet can too. The Rules Playground in the Firebase console lets you test signed-in cases, such as user A reading user B's document.

Check your repo automatically. Leakproof's free scanner flags Firebase rules left in test mode or set to if true, admin keys in client code, secrets in public env vars and more. It runs locally:

npx github:beingRealFrank/leakproof
Enter fullscreen mode Exit fullscreen mode

Want someone to go through all of this, plus your live project, and send fix PRs? See the fixed-price audits.


Written with AI assistance. Found a mistake? Open an issue at https://github.com/beingRealFrank/leakproof/issues.

Top comments (0)