How to secure a Firebase app built with AI coding tools
AI coding assistants and app builders are very good at getting a Firebase app working. They're less reliable at getting it secure. The fastest way to make an error go away is often to loosen a Security Rule, and "it works now" isn't the same as "only the right people can see the data".
This guide covers the gaps that show up most often in quickly built Firebase apps, how to fix each one, and how to check what your live project actually exposes.
Why AI-built apps are at risk
Firebase is client-first: your web or mobile app talks directly to Firestore, Realtime Database and Storage, and Security Rules are the only thing standing between the internet and your data. Your project ID and API key are in every copy of your app, and that's normal (they aren't secrets).
When an AI tool hits a permission-denied error, the quickest fix is to make the rule more permissive. Do that a few times while iterating, and you can end up shipping rules nobody actually reviewed.
The six gaps to check first
1. Test-mode or wide-open rules
// Firestore: open to everyone
match /{document=**} {
allow read, write: if true;
}
// Or test mode, open until a date
match /{document=**} {
allow read, write: if request.time < timestamp.date(2026, 12, 1);
}Fix: replace them with rules for each collection (below). Nothing in production should match /{document=**} with a permissive condition.
2. "Signed in" used as "allowed"
match /{document=**} {
allow read, write: if request.auth != null;
}This is the most common rule in AI-generated code, and it's almost never what you want. If sign-up is open, anyone can create an account using your public config, then read and edit every user's data.
Fix: check ownership:
match /users/{userId} {
allow read, write: if request.auth != null && request.auth.uid == userId;
}
match /posts/{postId} {
allow read: if true; // only if posts really are public
allow create: if request.auth != null
&& request.resource.data.authorId == request.auth.uid;
allow update, delete: if request.auth != null
&& resource.data.authorId == request.auth.uid;
}3. Users can edit their own role
A common pattern is a role: "user" or isAdmin: false field on the user's profile document, with the client hiding admin screens when it's false. If users can write to their own profile without field-level checks, they can set isAdmin: true themselves.
Fix: stop users changing protected fields:
match /users/{userId} {
allow read: if request.auth != null && request.auth.uid == userId;
allow update: if request.auth != null
&& request.auth.uid == userId
&& !request.resource.data.diff(resource.data).affectedKeys().hasAny(['role', 'isAdmin', 'plan']);
}Even better, store roles as custom claims set from a trusted server, and check request.auth.token.admin == true in your rules. Hiding a button in the UI is never access control.
4. Secrets in the client bundle
AI tools often wire up third-party APIs (payments, email, LLM APIs) directly from the frontend to get a feature working. Any key in client code, including NEXT_PUBLIC_*, VITE_* and EXPO_PUBLIC_* environment variables, is visible to anyone who opens DevTools.
Fix: move calls that need secret keys into Cloud Functions (or another backend), store the keys in a secret manager, and rotate any key that has ever shipped to the browser.
5. Cloud Functions that trust the caller
HTTP functions (onRequest) are public URLs. If a function does something privileged (sends email, charges a card, writes with admin rights) without verifying the caller's Firebase ID token, anyone can call it.
Fix: use callable functions (onCall), which verify Firebase Auth automatically, and check request.auth inside them. Or verify the ID token yourself in onRequest handlers. Remember the Admin SDK bypasses Security Rules, so every function using it needs its own authorization checks.
6. Open Storage buckets and deployed config files
- Storage rules like
allow read, write: if request.auth != nullacross/{allPaths=**}expose every uploaded file. See Firebase Storage security rules explained. - Build tools sometimes deploy files you didn't mean to publish, like
.envor.git/folders, to Firebase Hosting. Check yourfirebase.jsonpublicdirectory andignorelist.
A quick review prompt for your AI tool
If you're building with an AI assistant, ask it directly. Something like:
Review my
firestore.rules,storage.rulesanddatabase.rules.json. For each collection or path, explain who can read, list, create, update and delete. Flag any rule usingif true, a test-mode date, orrequest.auth != nullwithout an ownership or role check. Then propose least-privilege rules and emulator unit tests that prove user A can't access user B's data.
Treat the answer as a starting point, not a sign-off. Then verify the live project.
Check what's actually exposed
Your rules file and your deployed project can drift apart, especially when changes are made in the Firebase console. FireScan tests the live project from the outside, using only the public config an attacker would have:
go install github.com/JacobDavidAlcock/firescan/cmd/firescan@latest
firescan
firescan > set projectID your-project-id
firescan > set apiKey AIza...
firescan > scan --unauth # what's open with no login at all
firescan > auth --create-account # can a stranger sign up?
firescan > scan --all # what can that stranger read?The default mode is read-only, so it won't change your data. Work through the findings, fix your rules, deploy, and scan again until nothing unexpected shows up. You can also run it in CI so a future change doesn't quietly reopen something.
Only scan projects you own or have permission to test.
Checklist before you launch
- [ ] No
if true, test-mode dates or catch-all/{document=**}rules - [ ] Every rule checks ownership or a role, not just
request.auth != null - [ ] Users can't edit their own
role,isAdmin,planor similar fields - [ ] No secret keys in client code or public environment variables
- [ ] Cloud Functions verify the caller and authorize every privileged action
- [ ] Storage rules restrict paths,
list, file size and type - [ ] Unused sign-in providers are disabled, and App Check is considered
- [ ] Live project scanned with FireScan, with findings fixed and re-scanned
