TL;DR
Jeg sendte de samme forespørslene mot 11 Firestore-regelsett som gjest, anonym bruker og tre innloggede brukere. request.auth != null lot en fremmed lese, endre og slette alles ordrer. En eiersjekk kun på resource lot en bruker gi en ordre til en annen. Ett bredt {document=**}-match åpnet en samling som hadde en riktig eierregel. Og en rolle lagret i et dokument brukeren kan skrive, gjorde dem til admin i én forespørsel. Hver av dem har en løsning under, og den rettede regelen holdt mot de samme forespørslene.
I en Firebase-app finnes det ingen server av deg mellom nettleseren og databasen. Prosjektkonfigurasjonen følger med siden, og security rules er det eneste som står mellom alle som har den og dataene dine. Derfor ville jeg ha eksempler på regler med angrepet ved siden av, ikke en liste over regler som bare ser riktige ut.
Slik testet jeg. Firebase Local Emulator Suite fra Firebase CLI 15.34.2, 3. november 2026. Jeg lastet hvert regelsett, nullstilte dataene og kalte Firestore REST API med ekte tokens fra Auth-emulatoren: uten token, en anonym bruker, brukerne A og B og en fremmed som bare hadde registrert seg. Dataene var oppdiktede: ordrene a1 og a2 eies av A, b1 eies av B.
Hvilke Firebase-regler lekker?
| Regel | Forespørsel | Resultat |
|---|---|---|
if true (testmodus) |
Gjest lister ordrer | 200, alle 3 |
if true (testmodus) |
Gjest sletter Bs ordre | 200 |
request.auth != null |
Anonym bruker lister ordrer | 200, alle 3 |
request.auth != null |
Fremmed sletter As ordre | 200 |
Eiersjekk kun på resource |
A oppretter en ordre eid av B | 200 |
Eiersjekk kun på resource |
A endrer ownerId på egen ordre |
200 |
Eierregel pluss {document=**} read |
A leser Bs ordre | 200 |
Bruker kan skrive eget users-dokument |
A setter role: admin, leser Bs ordre |
200 |
Hva åpner testmodus egentlig?
match /{document=**} {
allow read, write: if true;
}
Alt, for alle. En gjest uten token listet alle 3 ordrene, slettet én og opprettet en ny. Ingenting av dette krever en konto eller en lekket hemmelighet, bare prosjekt-ID-en.
Den daterte versjonen som testmodus ofte bruker, feiler på en annen måte. Med if request.time < timestamp.date(2026, 1, 1) og den datoen i fortiden fikk gjesten 403, og det samme fikk bruker A som leste sin egen ordre. Lekkasjen er over, og det er appen også.
Er request.auth != null nok?
Nei. Den sier bare at forespørselen har en konto bak seg.
match /orders/{id} {
allow read, write: if request.auth != null;
}
Gjesten fikk 403, og det er derfor regelen føles trygg i en rask test. Så listet en fremmed som bare hadde registrert seg alle 3 ordrene, endret totalen på Bs ordre og slettet As ordre, alt med 200. En anonym bruker listet også alle 3. Hvis registreringen er åpen, betyr denne regelen «alle som gidder å registrere seg». Firebase-dokumentasjonen nevner den også som et vanlig usikkert mønster.
Hvorfor trenger en eiersjekk både resource og request.resource?
resource er dokumentet slik det er lagret. request.resource er dokumentet slik det ville vært etter skrivingen. En regel som bare ser på det første, sjekker hvem som eier raden nå, og sier ingenting om hva de gjør den til.
match /orders/{id} {
allow read, update, delete: if resource.data.ownerId == request.auth.uid;
allow create: if request.auth != null;
}
Lesing så riktig ut: A fikk 200 på sin egen ordre og 403 på Bs. Skriving gjorde det ikke. A opprettet en ny ordre med ownerId satt til B og fikk 200. Deretter endret A ownerId på sin egen ordre til B, også 200, og B kunne lese den fra da av. I en app der ordrer betyr fakturaer eller saldoer er det en bruker som skriver inn på en annens konto.
Denne versjonen avviste alt:
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;
}
| Forespørsel fra A | Resultat |
|---|---|
| Lese egen ordre | 200 |
| Lese, endre, slette Bs ordre | 403 |
| Opprette en ordre eid av B | 403 |
Endre ownerId til B |
403 |
Den lot likevel A endre total på sin egen ordre, med 200. Hvis klienten ikke skal sette det feltet, si hvilke felt den får endre:
allow update: if request.auth != null
&& resource.data.ownerId == request.auth.uid
&& request.resource.data.diff(resource.data).affectedKeys().hasOnly(['note']);
Med denne ga endring av total 403, og endring av note ga 200.
Hvorfor feilet spørringen min med en riktig regel?
Fordi regler ikke er filtre. Under eierregelen over fikk A 403 da den ba om hele samlingen, selv om A eier 2 av de 3 ordrene. Firestore vurderer spørringen etter hva den kunne returnere, ikke etter hva som faktisk finnes.
| Forespørsel fra A | Resultat |
|---|---|
| List alle ordrer | 403 |
Spørring ownerId == A |
200, 2 dok. |
Spørring ownerId == B |
403 |
Så når en liste slutter å virke etter at du har strammet inn reglene, fiks spørringen. Å løsne regelen for at listen skal virke er slik de brede reglene over blir skrevet.
Kan et bredt match oppheve en riktig regel?
Ja, og det er den jeg ville lett etter først i et prosjekt med en lang regelfil. Firestore tillater en forespørsel hvis en av de matchende reglene tillater 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 blokken ser ut som en god eierregel. Med den andre blokken til stede leste A Bs ordre med 200, en fremmed listet alle 3 ordrene, og den samme fremmede leste As dokument i users. Ingenting i den første blokken advarer deg. Slett det brede matchet; det finnes ingen måte å få en senere regel til å ta tilbake tilgang.
Hvordan gjør en bruker seg selv til admin?
Dette mønsteret dukker opp når 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();
}
«Brukere kan redigere sin egen profil» og «admins kan lese alle ordrer» er hver for seg fornuftige. Sammen er de en privilegieeskalering.
Løsningen er å hindre klienten i å skrive det feltet. Med request.resource.data.role == resource.data.role lagt til skrivereglen ga den samme forespørselen 403, og A holdt seg utenfor Bs ordre. Custom claims satt fra en server er den andre veien, siden en klient ikke kan endre dem.
To ting som viste seg å være greie
Regler på en forelder lekker ikke ned i subsamlinger. Med allow read på /orders/{id} for enhver innlogget bruker leste A ordren, og /orders/b1/notes/n1 under den ga 403. Det er dokumentert oppførsel: regler gjelder bare på den matchede stien.
En gjest slipper ikke gjennom en sammenligning med manglende token. resource.data.ownerId == request.auth.uid ga 403 for gjesten selv uten en egen request.auth != null. Jeg skriver sjekken ut likevel, fordi regelen da sier det den mener.
Hvordan sjekker du dine egne regler?
Kjør forespørslene dine i emulatoren som gjest, som fremmed og som en andre bruker. Det var det som avslørte hvert tilfelle over; å lese reglene gjorde det ikke.
For en første gjennomgang leser den gratis Firebase regelsjekk limte inn regler uten å røre prosjektet ditt. På regelsettene her flagget den testmodus, tilgang kun for innloggede, det brede matchet og den selvskrevne rollen. Den leser bare tekst, så den kan ikke si hva dataene dine ser ut som eller om en regel ble deployet.
apiKey på siden din er ikke problemet i seg selv. Firebase web-konfigurasjon er offentlig av design, og den gratis Secret scanner markerer den slik, fordi det er reglene som beskytter dataene bak. Filer følger igjen andre regler, med en lekkasje reglene ikke kan stoppe, som jeg testet i Firebase Storage rules.
Begrensninger. Bare Firestore, i den lokale emulatoren, over REST, på oppdiktede data. Realtime Database bruker et annet regelspråk og ble ikke testet. App Check, som begrenser hvilke apper som kan kalle backenden din, er ikke med her; det erstatter ikke regler. Produksjon kan avvike fra emulatoren i detaljer jeg ikke sjekket.
Usikker på hva reglene dine slipper gjennom?
Jeg leser dem gjerne sammen med deg. Ingen salgstale.
Ta kontakt