---
title: "Pravidla Firebase: 11 pravidel, která jsem napadl"
description: "Pravidla zabezpečení Firebase otestovaná v emulátoru. Co které pravidlo dovolilo hostovi či jinému uživateli, co zůstalo otevřené a co to zavřelo."
url: "https://madsalt.tech/cs/blog/firebase-security-rules-examples/"
author: "Denys Zaichenko"
author_url: "https://madsalt.tech/cs/about/"
language: "cs"
published: 2026-11-03
---

# Pravidla Firebase: 11 pravidel, která jsem napadl

TL;DR

Na 11 sad pravidel Firestore jsem poslal stejné požadavky jako host, anonymní uživatel a tři přihlášení uživatelé. `request.auth != null` nechalo cizího člověka číst, měnit i mazat cizí objednávky. Kontrola vlastníka jen přes `resource` nechala uživatele předat objednávku někomu jinému. Jeden široký match `{document=**}` otevřel kolekci, která měla správné pravidlo pro vlastníka. A role uložená v dokumentu, který uživatel smí zapisovat, z něj udělala admina jedním požadavkem. U každého případu je níže oprava a opravené pravidlo obstálo proti stejným požadavkům.

Mezi prohlížečem a databází aplikace na Firebase není žádný váš server. Konfigurace projektu je součástí stránky a [pravidla zabezpečení](https://firebase.google.com/docs/rules/insecure-rules) jsou to jediné, co stojí mezi kýmkoli, kdo ji má, a vašimi daty. Chtěl jsem proto příklady pravidel s útokem hned vedle, ne seznam pravidel, která jen vypadají správně.

**Jak jsem testoval.** Firebase Local Emulator Suite z Firebase CLI 15.34.2, 3. listopadu 2026. Každou sadu pravidel jsem načetl, vynuloval data a volal REST API Firestore se skutečnými tokeny z emulátoru Auth: bez tokenu, anonymní uživatel, uživatelé A a B a cizí člověk, který se jen zaregistroval. Data byla vymyšlená: objednávky `a1` a `a2` patří A, `b1` patří B.

## Která pravidla Firebase propouštějí data?

| Pravidlo                                      | Požadavek                                    | Výsledek         |
| --------------------------------------------- | -------------------------------------------- | ---------------- |
| `if true` (testovací režim)                   | Host vypíše objednávky                       | `200`, všechny 3 |
| `if true` (testovací režim)                   | Host smaže objednávku B                      | `200`            |
| `request.auth != null`                        | Anonymní uživatel vypíše objednávky          | `200`, všechny 3 |
| `request.auth != null`                        | Cizí uživatel smaže objednávku A             | `200`            |
| Kontrola vlastníka jen přes `resource`        | A vytvoří objednávku patřící B               | `200`            |
| Kontrola vlastníka jen přes `resource`        | A změní `ownerId` své objednávky             | `200`            |
| Pravidlo vlastníka plus čtení `{document=**}` | A přečte objednávku B                        | `200`            |
| Uživatel smí zapisovat svůj dokument `users`  | A nastaví `role: admin`, přečte objednávku B | `200`            |

## Co vlastně otevírá testovací režim?

```
match /{document=**} {
  allow read, write: if true;
}
```

Všechno a všem. Host bez tokenu vypsal všechny 3 objednávky, jednu smazal a vytvořil novou. K ničemu z toho nepotřebuje účet ani uniklé tajemství, stačí id projektu.

Datovaná verze, kterou testovací režim často používá, selhává jinak. S `if request.time < timestamp.date(2026, 1, 1)` a datem v minulosti dostal host `403` a stejně tak uživatel A při čtení vlastní objednávky. Únik skončil, ale skončila i aplikace.

## Stačí `request.auth != null`?

Ne. Říká jen, že za požadavkem stojí nějaký účet.

```
match /orders/{id} {
  allow read, write: if request.auth != null;
}
```

Host dostal `403`, proto toto pravidlo při rychlém testu vypadá bezpečně. Pak ale cizí člověk, který se jen zaregistroval, vypsal všechny 3 objednávky, změnil částku na objednávce B a smazal objednávku A, pokaždé s `200`. Anonymní uživatel vypsal všechny 3 také. Pokud je registrace otevřená, znamená toto pravidlo „každý, kdo se obtěžuje zaregistrovat“. Dokumentace Firebase ho uvádí také jako [častý nebezpečný vzor](https://firebase.google.com/docs/rules/insecure-rules).

## Proč kontrola vlastníka potřebuje `resource` i `request.resource`?

`resource` je dokument tak, jak je uložený. `request.resource` je dokument tak, jak by vypadal po zápisu. Pravidlo, které se dívá jen na první, kontroluje, komu řádek patří teď, a nic neříká o tom, na co ho uživatel přepíše.

```
match /orders/{id} {
  allow read, update, delete: if resource.data.ownerId == request.auth.uid;
  allow create: if request.auth != null;
}
```

Čtení vypadalo správně: A dostal `200` na svou objednávku a `403` na objednávku B. Zápisy ne. A vytvořil novou objednávku s `ownerId` nastaveným na B a dostal `200`. Pak A změnil `ownerId` na své objednávce na B, také `200`, a B ji od té chvíle mohl číst. V aplikaci, kde objednávky znamenají faktury nebo zůstatky, to je uživatel, který zapisuje do cizího účtu.

Tato verze odmítla všechno:

```
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;
}
```

| Požadavek od A                  | Výsledek |
| ------------------------------- | -------- |
| Přečíst vlastní objednávku      | `200`    |
| Číst, měnit, mazat objednávku B | `403`    |
| Vytvořit objednávku patřící B   | `403`    |
| Změnit `ownerId` na B           | `403`    |

Stále ale dovolilo A změnit `total` na vlastní objednávce, s `200`. Pokud klient to pole nastavovat nesmí, napište, která pole smí měnit:

```
allow update: if request.auth != null
  && resource.data.ownerId == request.auth.uid
  && request.resource.data.diff(resource.data).affectedKeys().hasOnly(['note']);
```

Díky tomu změna `total` vrátila `403` a změna `note` vrátila `200`.

## Proč můj dotaz selhal, i když bylo pravidlo správné?

Protože [pravidla nejsou filtry](https://firebase.google.com/docs/firestore/security/rules-query). Při výše uvedeném pravidle vlastníka dostal A na dotaz na celou kolekci `403`, přestože vlastní 2 ze 3 objednávek. Firestore posuzuje dotaz podle toho, co by mohl vrátit, ne podle toho, co v databázi je.

| Požadavek od A            | Výsledek      |
| ------------------------- | ------------- |
| Vypsat všechny objednávky | `403`         |
| Dotaz `ownerId == A`      | `200`, 2 dok. |
| Dotaz `ownerId == B`      | `403`         |

Když se tedy po zpřísnění pravidel rozbije výpis, opravte dotaz. Uvolňovat pravidlo, aby výpis fungoval, je přesně způsob, jak vznikají ta široká pravidla výše.

## Může široký match zrušit správné pravidlo?

Ano, a na tohle bych se v projektu s dlouhým souborem pravidel díval jako na první. Firestore [povolí požadavek, pokud ho povolí kterékoli odpovídající pravidlo](https://firebase.google.com/docs/firestore/security/rules-structure).

```
match /orders/{id} {
  allow read: if request.auth != null
    && resource.data.ownerId == request.auth.uid;
}
match /{document=**} {
  allow read: if request.auth != null;
}
```

První blok vypadá jako dobré pravidlo pro vlastníka. S přítomností druhého bloku A přečetl objednávku B s `200`, cizí uživatel vypsal všechny 3 objednávky a stejný uživatel přečetl dokument A v `users`. První blok nijak nevaruje. Široký match smažte; pozdější pravidlo nemá jak přístup vzít zpět.

## Jak si uživatel udělá admina?

Tento vzor se objevuje všude, kde jsou role v dokumentu:

```
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();
}
```

„Uživatelé smějí upravovat svůj profil“ a „admini smějí číst všechny objednávky“ jsou každé samo o sobě rozumné. Dohromady je to zvýšení oprávnění.

![Admin jedním požadavkem. Uživatel A, jehož vlastní dokument users lze zapisovat. Před: Uživatel A → čte objednávku B → 403; Zápis vlastní role: Uživatel A → role: admin → 200; Po: Uživatel A → čte objednávku B → 200.](https://madsalt.tech/charts/cs/firebase-security-rules-examples.svg)

*Obrázek 1. Můj test v emulátoru Firebase (CLI 15.34.2), 3. listopadu 2026.*

Oprava je zabránit klientovi zapisovat to pole. Po přidání `request.resource.data.role == resource.data.role` do pravidla pro zápis vrátil stejný požadavek `403` a A zůstal mimo objednávku B. Druhou cestou jsou vlastní claims nastavené ze serveru, protože klient je změnit nemůže.

## Dvě věci, které dopadly dobře

Pravidla na nadřazené cestě neprosakují do podkolekcí. S `allow read` na `/orders/{id}` pro libovolného přihlášeného uživatele A objednávku přečetl a `/orders/b1/notes/n1` pod ní vrátila `403`. To je [zdokumentované chování](https://firebase.google.com/docs/firestore/security/rules-structure): pravidla platí jen na odpovídající cestě.

Host neprojde přes porovnání s chybějícím tokenem. `resource.data.ownerId == request.auth.uid` vrátilo hostovi `403`, i bez samostatného `request.auth != null`. Přesto tu kontrolu píšu, protože pak pravidlo říká, co znamená.

## Jak zkontrolovat vlastní pravidla?

Spusťte své požadavky v emulátoru jako host, jako cizí uživatel a jako druhý uživatel. To odhalilo každý případ výše; čtení pravidel ne.

Pro první průchod čte bezplatná [Kontrola pravidel Firebase](https://madsalt.tech/cs/tools/firebase-rules-checker/) vložená pravidla, aniž by se dotkla vašeho projektu. Na zde uvedených sadách označila testovací režim, přístup jen po přihlášení, široký match a roli, kterou si uživatel zapisuje sám. Čte jen text, takže nepozná, jak vypadají vaše data ani zda bylo pravidlo nasazeno.

Samotný `apiKey` ve stránce problém není. Webová konfigurace Firebase je veřejná záměrně a bezplatný [Skener tajných klíčů](https://madsalt.tech/cs/tools/secret-scanner/) ji tak označuje, protože data za ní chrání pravidla. Soubory se řídí zase jinými pravidly, s únikem, který pravidla zastavit neumějí, a ten jsem testoval v článku pravidla Firebase Storage.

**Limity.** Jen Firestore, v lokálním emulátoru, přes REST, na vymyšlených datech. Realtime Database používá jiný jazyk pravidel a netestoval jsem ji. App Check, který omezuje, které aplikace smějí volat váš backend, tu není; pravidla nenahrazuje. Produkce se od emulátoru může v detailech lišit, které jsem nekontroloval.

Nevíte, co vaše pravidla propouštějí?

Rád je s vámi projdu. Bez nabídky.

[Ozvěte se](https://madsalt.tech/cs/#contact)
