Firebase Rules Analyzer
With Firebase the rules file is the whole server-side access control: the browser talks to the database directly, so a permissive rule is an open database. The analyzer reads firestore.rules, storage.rules and database.rules.json as text and reports rules that grant everything, rules with no auth test and broad wildcards.
A rules file, line by line
The scanner is the only actor and the target is a text file. Each step shows the pattern it runs, the line that answers it and the severity that comes back.
1The scanner picks a file whose path ends in firestore.rules, storage.rules or database.rules.json, then decides which service it is from the text. Rules are the only access control, because a Firebase client talks to the database directly.
2Pattern one is the literal allow read, write: if true. Critical, 0.95: anyone, signed in or not, can read and write whatever this match block covers.
3Pattern two is allow read: if true on its own; a third does the same for write. High, 0.95 for each, once per file. Open reads leak data but cannot be used to plant it.
4Every other allow line is checked for an auth test in a three-line window. request.auth != null satisfies it, so line 8 is accepted even though any signed-in user may write any post.
5allow delete: if isAdmin() has no request.auth within three lines. High, 0.85. The check is textual: it does not follow isAdmin() to its definition, so a helper that does verify auth is still reported.
6A match /{document=**} catch-all is reported at Medium, 0.75, because it may expose collections the author never meant to cover. The rule inside it is a plain auth check, so nothing else fires here.
Realtime Database
The older database keeps its rules in JSON. The analyzer treats the file as text and looks for exactly two shapes.
1A path ending in .json is treated as Realtime Database rules. The text is searched, not parsed, so the nesting of the rule tree plays no part.
2".read": true grants the whole tree to the internet. Critical, 0.95. A literal ".write": true would be a second Critical finding.
3Rules written as strings, "auth != null" or "$uid === auth.uid", are never matched. Only the boolean literal true is flagged, so a too-broad expression is not reported here.
One rule, one request
A rule is a condition that Firebase evaluates against each request. Here is the same document read four times, with the verdict Firebase gives and the verdict isitsecure gives on the file. They differ in step 3, and that difference is what the check does not cover.
1An anonymous visitor asks for /users/alice. The rule is if true, so Firestore returns the document. isitsecure catches this one from the file alone: High.
2Same visitor, rule if request.auth != null. With no token request.auth is null, so the condition is false and Firestore refuses. The scanner stays quiet, correctly.
3Bob signs in and asks for Alice's document under the same rule. request.auth is not null, so Firestore allows it. The rule never compares the uid to the path. The scanner is silent here too, and this is the gap in the check.
4Add request.auth.uid == userId. Bob's uid is not Alice's, so the read is denied. This is the owner-only shape from the fix section, and the scanner accepts it.
Why it matters
Firebase security rules are the only access control for Firestore, Realtime Database and Storage. There is no backend API to add an auth check to. If the rules are permissive, anyone can read all data, anyone can write it, and nothing sits behind the database to stop them.
Security researchers have published scans finding thousands of Firebase databases with open rules. In 2020 a study by Comparitech found 24,000 Android apps with misconfigured Firebase databases leaking user data, including emails, passwords and health data.
Vulnerable vs fixed rules
// BAD: wide open
match /users/{userId} {
allow read, write: if true;
}
// BAD: no auth test anywhere near the allow
match /logs/{logId} {
allow delete: if isAdmin();
}// GOOD: owner-only access
match /users/{userId} {
allow read: if request.auth != null && request.auth.uid == userId;
allow write: if request.auth != null && request.auth.uid == userId;
}
// GOOD: validate data on write (not checked by the analyzer)
match /posts/{postId} {
allow create: if request.auth != null
&& request.resource.data.authorId == request.auth.uid
&& request.resource.data.title is string
&& request.resource.data.title.size() <= 200;
}A missing auth test is reported, but request.auth != null with no ownership check is accepted. Missing rules for a collection and missing validation rules are not checked.
Limits and scope
| Behaviour | Detail |
|---|---|
| Input | Files whose path ends in firestore.rules, storage.rules, database.rules.json, or the same names under firebase/. The repository only; no live Firebase project is contacted. |
| Severity and confidence | Open read and write together: Critical 0.95. Open read or write alone: High 0.95. Missing auth test: High 0.85. Wildcard: Medium 0.75. Realtime Database true rules: Critical 0.95 each. |
| Open rules | Reported once per file and pattern, with the first matching line. Only the literal if true counts. |
| Missing auth test | Looks at an allow read|write|create|update|delete: line and the two after it. Multi-operation lines such as allow read, write: if x do not match this pattern, so only the exact if true form of them is caught. |
| What counts as auth | request.auth != null or any mention of request.auth.uid. Ownership is not checked. Helper functions are not followed. |
| Realtime Database | Two regular expressions on the text. String expressions are not evaluated. |
| Not implemented | Missing rules for collections, data validation checks, ownership checks on auth != null. |