TL;DR
Jeg sendte de samme anmodninger mod 11 Firestore-regelsæt som gæst, anonym bruger og tre loggede brugere. request.auth != null lod en fremmed læse, ændre og slette alles ordrer. Et ejertjek alene på resource lod en bruger give en ordre videre til en anden. Ét bredt {document=**}-match åbnede en samling, der havde en korrekt ejerregel. Og en rolle gemt i et dokument, brugeren selv kan skrive til, gjorde dem til admin med én anmodning. Hver af dem har en rettelse nedenfor, og den rettede regel holdt mod de samme anmodninger.
En Firebase-app har ingen server af din egen mellem browseren og databasen. Projektets konfiguration ligger i siden, og security rules er det eneste, der står mellem enhver, der har den, og dine data. Så jeg ville have eksempler på regler med angrebet ved siden af, ikke en liste over regler, der bare ser rigtige ud.
Sådan testede jeg. Firebase Local Emulator Suite fra Firebase CLI 15.34.2, den 3. november 2026. Jeg indlæste hvert regelsæt, nulstillede data og kaldte Firestore REST API med rigtige tokens fra Auth-emulatoren: intet token, en anonym bruger, brugerne A og B og en fremmed, der kun havde oprettet sig. Data var opfundne: ordrerne a1 og a2 ejes af A, b1 ejes af B.
Hvilke Firebase-regler lækker?
| Regel | Anmodning | Resultat |
|---|---|---|
if true (testtilstand) |
Gæst lister ordrer | 200, alle 3 |
if true (testtilstand) |
Gæst sletter Bs ordre | 200 |
request.auth != null |
Anonym bruger lister ordrer | 200, alle 3 |
request.auth != null |
Fremmed sletter As ordre | 200 |
Ejertjek kun på resource |
A opretter en ordre ejet af B | 200 |
Ejertjek kun på resource |
A ændrer ownerId på egen ordre |
200 |
Ejerregel plus læsning på {document=**} |
A læser Bs ordre | 200 |
Bruger kan skrive eget users-dokument |
A sætter role: admin, læser Bs ordre |
200 |
Hvad åbner testtilstand egentlig?
match /{document=**} {
allow read, write: if true;
}
Alt, for alle. En gæst uden token listede alle 3 ordrer, slettede én og oprettede en ny. Intet af det kræver en konto eller en lækket hemmelighed, kun projekt-id’et.
Den daterede version, som testtilstand ofte bruger, fejler på en anden måde. Med if request.time < timestamp.date(2026, 1, 1) og den dato i fortiden fik gæsten 403, og det samme gjorde bruger A, der læste sin egen ordre. Lækagen er forbi, og det er appen også.
Er request.auth != null nok?
Nej. Den siger kun, at der er en eller anden konto bag anmodningen.
match /orders/{id} {
allow read, write: if request.auth != null;
}
Gæsten fik 403, og derfor føles reglen sikker i en hurtig test. Så listede en fremmed, der kun havde oprettet sig, alle 3 ordrer, ændrede totalen på Bs ordre og slettede As ordre, hver gang med 200. En anonym bruger listede også alle 3. Hvis tilmelding er åben, betyder reglen “alle, der gider registrere sig”. Firebase-dokumentationen nævner den også som et almindeligt usikkert mønster.
Hvorfor skal et ejertjek bruge både resource og request.resource?
resource er dokumentet, som det er gemt. request.resource er dokumentet, som det ville se ud efter skrivningen. En regel, der kun ser på det første, tjekker, hvem der ejer rækken nu, og siger intet om, hvad den bliver lavet om til.
match /orders/{id} {
allow read, update, delete: if resource.data.ownerId == request.auth.uid;
allow create: if request.auth != null;
}
Læsning så rigtig ud: A fik 200 på sin egen ordre og 403 på Bs. Skrivning gjorde ikke. A oprettede en ny ordre med ownerId sat til B og fik 200. A ændrede derefter ownerId på sin egen ordre til B, også 200, og B kunne læse den fra da af. I en app, hvor ordrer betyder fakturaer eller saldi, er det en bruger, der skriver ind på en andens konto.
Denne version afviste det hele:
match /orders/{id} {
allow read, delete: if request.auth != null
&& resource.data.ownerId == request.auth.uid;
allow create: if request.auth != null
&& request.resource.data.ownerId == request.auth.uid;
allow update: if request.auth != null
&& resource.data.ownerId == request.auth.uid
&& request.resource.data.ownerId == resource.data.ownerId;
}
| Anmodning fra A | Resultat |
|---|---|
| Læs egen ordre | 200 |
| Læs, opdater, slet Bs ordre | 403 |
| Opret en ordre ejet af B | 403 |
Ændr ownerId til B |
403 |
Den lod stadig A ændre total på sin egen ordre, med 200. Hvis klienten ikke må sætte det felt, så skriv, hvilke felter den må ændre:
allow update: if request.auth != null
&& resource.data.ownerId == request.auth.uid
&& request.resource.data.diff(resource.data).affectedKeys().hasOnly(['note']);
Med den gav en ændring af total 403, og en ændring af note gav 200.
Hvorfor fejlede min forespørgsel med en korrekt regel?
Fordi regler ikke er filtre. Under ejerreglen ovenfor fik A 403, da A bad om hele samlingen, selvom A ejer 2 af de 3 ordrer. Firestore vurderer forespørgslen ud fra, hvad den kunne returnere, ikke ud fra, hvad der er.
| Anmodning fra A | Resultat |
|---|---|
| List alle ordrer | 403 |
Forespørg ownerId == A |
200, 2 dok. |
Forespørg ownerId == B |
403 |
Så når en liste går i stykker, efter du har strammet reglerne, så ret forespørgslen. At løsne reglen for at få listen til at virke er sådan, de brede regler ovenfor bliver skrevet.
Kan et bredt match ophæve en korrekt regel?
Ja, og det er den, jeg ville lede efter først i et projekt med en lang regelfil. Firestore tillader en anmodning, hvis en matchende regel tillader den.
match /orders/{id} {
allow read: if request.auth != null
&& resource.data.ownerId == request.auth.uid;
}
match /{document=**} {
allow read: if request.auth != null;
}
Den første blok ligner en god ejerregel. Med den anden blok til stede læste A Bs ordre med 200, en fremmed listede alle 3 ordrer, og den samme fremmede læste As dokument i users. Intet i den første blok advarer dig. Slet det brede match; der er ingen måde, en senere regel kan tage adgang tilbage.
Hvordan gør en bruger sig selv til admin?
Mønsteret dukker op, hver gang roller ligger i et dokument:
function isAdmin() {
return get(/databases/$(database)/documents/users/$(request.auth.uid)).data.role == 'admin';
}
match /users/{uid} {
allow read, write: if request.auth.uid == uid;
}
match /orders/{id} {
allow read: if resource.data.ownerId == request.auth.uid || isAdmin();
}
“Brugere kan redigere deres egen profil” og “admins kan læse alle ordrer” er hver for sig rimelige. Sammen er de en rettighedseskalering.
Løsningen er at stoppe klienten i at skrive det felt. Med request.resource.data.role == resource.data.role tilføjet til skrivereglen gav den samme anmodning 403, og A forblev uden for Bs ordre. Custom claims sat fra en server er den anden vej, da en klient ikke kan ændre dem.
To ting, der viste sig at være i orden
Regler på en forælder lækker ikke ned i undersamlinger. Med allow read på /orders/{id} for enhver loggeret bruger læste A ordren, og /orders/b1/notes/n1 under den gav 403. Det er den dokumenterede opførsel: regler gælder kun på den matchede sti.
En gæst slipper ikke igennem en sammenligning med et manglende token. resource.data.ownerId == request.auth.uid gav 403 til gæsten, også uden et separat request.auth != null. Jeg skriver alligevel tjekket ud, fordi reglen så siger, hvad den mener.
Hvordan tjekker du dine egne regler?
Kør dine anmodninger i emulatoren som gæst, som fremmed og som en anden bruger. Det afslørede alle tilfældene ovenfor; at læse reglerne gjorde ikke.
Til en første gennemgang læser det gratis Firebase regeltjek indsatte regler uden at røre dit projekt. På regelsættene her flagede det testtilstand, adgang kun med login, det brede match og den selvskrevne rolle. Det læser kun tekst, så det kan ikke fortælle dig, hvordan dine data ser ud, eller om en regel er udrullet.
Din apiKey i siden er ikke problemet i sig selv. Firebase web-konfiguration er offentlig med vilje, og den gratis Secret scanner markerer den sådan, fordi det er reglerne, der beskytter dataene bag den. Filer følger igen andre regler, med en lækage som regler ikke kan stoppe, og den testede jeg i Firebase Storage rules.
Begrænsninger. Kun Firestore, i den lokale emulator, over REST, på opfundne data. Realtime Database bruger et andet regelsprog og blev ikke testet. App Check, som begrænser, hvilke apps der må kalde din backend, er ikke med; den erstatter ikke regler. Produktion kan afvige fra emulatoren i detaljer, jeg ikke har tjekket.
Usikker på, hvad dine regler lader slippe igennem?
Jeg læser dem gerne sammen med dig. Ingen salgstale.
Kontakt mig