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.
From your project folder:
grep -nE "if true|request.time <|\"\.(read|write)\": *true" firestore.rules storage.rules database.rules.json
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.
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;
}
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.
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']);
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" }
}
}
}
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/.*');
}
}
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"'
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.
Signed out, a private collection must refuse to list:
curl "https://firestore.googleapis.com/v1/projects/<project-id>/databases/(default)/documents/<collection>?pageSize=5"
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.
if true, admin keys in client code, secrets in public env vars and more. It runs locally:
npx github:beingRealFrank/leakproofWant someone to go through all of this, plus your live project, and send fix PRs? See the fixed-price audits.
More checklists: Supabase RLS · Secret keys in your frontend