---
title: "Jak testovat Supabase RLS: 30 z 87 oprávnění nedělalo nic"
description: "Testy Supabase RLS po jednotlivých politikách nevidí, co vidí uživatel. Ověření role po roli na kopii produkce našlo 30 z 87 oprávnění bez účinku."
url: "https://madsalt.tech/cs/blog/how-to-test-supabase-rls/"
author: "Denys Zaichenko"
language: "cs"
published: 2026-10-14
---

# Jak testovat Supabase RLS: 30 z 87 oprávnění nedělalo nic

TL;DR

Testujte Supabase row level security (zabezpečení na úrovni řádků) tak, jak ho zažívá člen týmu: přihlaste se za každou roli, spusťte skutečnou akci s jedním oprávněním vypnutým a pak zapnutým a ověřte, že se výsledek změní. Na produkční aplikaci s pěti upravitelnými rolemi takto vyšlo najevo, že 30 z 87 políček oprávnění, která mohl administrátor uložit, v databázi nic neměnilo. Test po jednotlivých politikách to neodhalí, protože testuje politiku, kterou jste napsali, ne políčko na obrazovce.

Jsem Denys, full-stack a AI inženýr. Letos jsem pro auditorskou firmu postavil na Supabase evidenci projektů a odpracovaného času, kde každé přístupové pravidlo žije v databázi. Níže je metoda, kterou ta pravidla ověřuji, co našla a co stojí její provoz.

## Proč k testování Supabase RLS nestačí testy politik?

Test politiky odpovídá na otázku „dělá tato politika to, co jsem napsal?“. Vaši uživatelé se ptají jinak: „když této roli odškrtnu políčko, může to pořád udělat?“. V malé aplikaci mají obě otázky stejnou odpověď. V aplikaci se stránkou nastavení, kde administrátoři upravují matici oprávnění, se od sebe vzdalují.

Aplikace měla 41 tabulek se zapnutým RLS a 402 příkazů `CREATE POLICY` v 255 migracích. Obrazovka administrátora ukazovala matici oblastí (projekty, úkoly, výkazy práce, soubory a další) proti akcím (zobrazit, vytvořit, upravit, archivovat), jeden sloupec na roli. Každé uložené políčko byl slib administrátorovi. Nic nekontrolovalo, že ho databáze dodržuje.

Supabase standardní nástroje dobře dokumentuje: [testy pgTAP](https://supabase.com/docs/guides/database/extensions/pgtap) a [testovací helpery](https://supabase.com/docs/guides/local-development/testing/pgtap-extended) umožňují vytvářet uživatele, přepínat se na ně a ověřovat, co smějí číst. Používejte je. Metoda níže na nich stojí: řídí ji matice, kterou vidí administrátor, ne seznam politik.

## Jak funguje ověření matice?

Z každého políčka matice se stane sonda. Sonda se přihlásí jako člen role, provede jednu skutečnou akci a zaznamená výsledek. Udělá to dvakrát: jednou s vypnutým políčkem a jednou se zapnutým, vše ostatní stejné. Když se výsledek změní, políčko se vynucuje. Když ne, políčko nedělá nic, ať na obrazovce vypadá jakkoli přesvědčivě.

1. **Jedno políčko matice**. Příklad: Standard Member · Úkoly · Vytvořit
2. **Políčko vypnuté: jednat jako člen role**. Vytvořit úkol stejnou cestou, jakou používá aplikace. Zaznamenat výsledek.
3. **Políčko zapnuté: zopakovat stejnou akci**. Stejný uživatel, stejná data. Změnilo se jen políčko.
4. **Porovnat oba výsledky**. Verdikt závisí jen na tom, co udělala databáze.
- **Výsledek se změnil**. Vynucuje se. Vypnuté odmítne, zapnuté povolí.
- **Oba výsledky stejné**. Nedělá nic. Obrazovka slibuje kontrolu, která neexistuje.
- **Obrazovka a databáze se neshodují**. Chyba. Stránka ukazuje tlačítko, které databáze odmítne, nebo naopak.

*Obrázek 1. Každý verdikt vychází z dvojice skutečných výsledků, nikdy ze čtení kódu.*

„Výsledek“ tu znamená cokoli, co dělá ten, kdo oprávnění používá: politika vrátí řádky nebo odmítne zápis, databázová funkce vyhodí výjimku, trigger zablokuje nebo edge funkce odpoví 403. Jedno políčko může hlídat několik z nich, takže 87 políček vyžadovalo 279 kontrol na roli. Každá kontrola skončila jedním z těchto verdiktů:

| Verdikt                         | Kontrol na roli | Co znamená                                                      |
| ------------------------------- | --------------- | --------------------------------------------------------------- |
| Účinek                          | 227             | Výsledek se s políčkem překlopil                                |
| Mrtvé                           | 30              | Výsledek zůstal stejný s políčkem vypnutým i zapnutým           |
| Nedosažitelné                   | 19              | Akce je pro tuto roli zcela nedosažitelná, ověřeno zvlášť       |
| Edge funkce                     | 1               | Vynucuje edge funkce, kontrolováno přes HTTP                    |
| Jen obrazovka                   | 1               | Mění jen to, co stránka zobrazuje, pokryto front-endovým testem |
| Obrazovka a databáze se shodují | 1               | Co stránka nabízí, odpovídá tomu, co databáze povolí            |
| Selhání                         | 0               | Kontrola, jejíž výsledek neodpovídal očekávání                  |

## Co ověření našlo?

Běh 15. září 2026 pokryl všech pět rolí, které může administrátor upravovat (Admin, Senior, Standard, Restricted a jednu klonovanou vlastní roli). Výsledky byly u všech rolí shodné.

![87 políček oprávnění, která mohl administrátor uložit. Podle toho, co každé políčko skutečně vynucovalo. Stejně u všech pěti rolí. Vynucuje databáze: 55; Vynucuje něco jiného: 1 edge funkce, 1 obrazovka: 2; Nic neměnilo: 30.](https://madsalt.tech/charts/cs/how-to-test-supabase-rls.svg)

*Obrázek 2. 87 políček oprávnění, která mohl administrátor uložit, podle toho, co je skutečně vynucovalo. Stejný výsledek u každé z pěti rolí.*

Žádné z těch 30 nikoho nepustilo dovnitř. U 29 z nich políčko nikdy nečetla žádná politika, funkce ani trigger; to poslední přestalo hrát roli po přepsání jedné politiky. Pořád je to skutečný problém: administrátor, který roli odškrtne „Smazat“, věří, že role mazat nemůže. Všech 30 jsme skryli, místo abychom je napojili, protože každé by vyžadovalo produktové rozhodnutí, a políčko, které nic nedělá, nemá být na obrazovce, dokud se rozhoduje.

Stejná práce odhalila další čtyři vady:

| Co bylo špatně                                                                                     | Jak se to projevilo                                     | Oprava                                                        |
| -------------------------------------------------------------------------------------------------- | ------------------------------------------------------- | ------------------------------------------------------------- |
| 30 políček nedělalo nic                                                                            | Verdikt Mrtvé, stejný výsledek vypnuto i zapnuto        | Skryta z matice                                               |
| Tři databázové funkce pustily akci, když úkol neměl řešitele                                       | Sonda, která měla být odmítnuta, prošla                 | Funkce teď odmítnou, když hodnota chybí                       |
| Vrácení výkazu práce do konceptu záviselo na špatném políčku                                       | Obrazovka a databáze se u jedné role neshodovaly        | Váže se na oprávnění k znovuotevření, kam patří               |
| Člen viděl nová oprávnění až po obnovení stránky                                                   | Databáze se změnila, stránka ne                         | Oprávnění se obnovují každých 60 sekund a při návratu do okna |
| Výchozí hodnota sloupce porušovala jeho vlastní check constraint, takže inserty bez sloupce padaly | Našla ji bezpečnostní revize opravy, během stejné práce | Výchozí hodnota změněna na takovou, kterou constraint povolí  |

Podstatná byla chyba s chybějícím řešitelem. Kontroly byly v PL/pgSQL napsané takto:

```sql
if not (has_permission or assignee_id = auth.uid()) then
  raise exception 'forbidden';
end if;
```

Když je `assignee_id` null, je `assignee_id = auth.uid()` null, `false or null` je null, `not null` je stále null a `if` bere null jako false. Výjimka se nikdy nevyhodí. V produkci neměl žádný úkol řešitele nastaveného, takže nešlo o okrajový případ: týkalo se to každého úkolu v rozsahu. Oprava obalí podmínku do `coalesce(…, false)`, aby chybějící hodnota znamenala odmítnutí, a šla ven jako první, samostatně, před zbytkem dávky.

## Jak to spustit proti kopii produkce?

Ověření proti schématu, které se liší od produkce, o produkci nic nedokazuje. Harness běží na lokálním stacku Supabase, v samostatné databázi, která je kopií produkčního schématu plus migrace v revizi. Před každým během porovná otisk obou lokálních databází: 799 řádků popisujících tabulky, politiky, funkce a granty. Jakýkoli rozdíl běh zastaví.

1. **Produkční schéma**. Zkopírované na jeden důvěryhodný stroj. Nikdy nejde na hostovaný CI runner.
2. **Lokální stack: databáze aplikace**. Obsluhovaná lokálním API a auth, používaná end-to-end kontrolami.
3. **Lokální stack: izolovaná databáze pro ověření**. Stejné schéma, používá ji jen ověření matice.
4. **Kontrola otisku**. 799 řádků na databázi. Jakýkoli rozdíl zastaví běh před první sondou.
5. **Sondy běží za každou roli**. Jako skuteční přihlášení členové, stejnými cestami, jaké používá aplikace.
6. **Bez stopy**. Počty řádků na konci se musí rovnat počtům na začátku.

*Obrázek 3. Ověření platí jen tehdy, když databáze, na které běželo, odpovídá produkci.*

Dvě věci udělaly harness důvěryhodným:

- **Nejdřív ho schválně rozbijte.** Než jsem uvěřil zelenému běhu, nahradil jsem jednu insert politiku takovou, která přijme cokoli, a spustil ověření znovu. Muselo zčervenat na políčkách, která ta politika hlídá. Harness, který zůstane zelený s rozbitou politikou, je jen dekorace.
- **Kontrolujte i uložený stav.** První průchod začíná u každé role od čisté výchozí matice. Druhý začíná od aktuálně uložené matice každé role, protože políčko se může chovat jinak vedle políček, která administrátor skutečně zaškrtl.

## Jak udržet ověření zelené i po vydání?

Ověření, které spustíte jednou, zastará s další migrací. Teď běží před každým pushem, který se dotkne migrací, auth kódu nebo samotné matice.

1. **git push**. Změna se dotýká migrací, auth nebo matice.
2. **Pre-push hook: ověření matice**. Běží lokálně proti databázi pro ověření. Červená push zablokuje.
3. **Push dorazí do main**. Nasazení proběhne jako obvykle.
4. **Běh na self-hosted CI**. Stejné ověření znovu, na stroji s kopií produkčního schématu.

*Obrázek 4. Blokující brána je lokální, protože kopie produkčního schématu zůstává na jednom stroji.*

Ověření tvoří 135 testů v 11 souborech, vedle zhruba 3 100 běžných unit testů ve stejném projektu.

## Co nefungovalo?

- **Hostované CI ho spustit nemohlo.** Ověření potřebuje kopii produkčního schématu a tu jsem na hostovaném runneru mít nechtěl. Blokující bránou je proto pre-push hook a CI na self-hosted stroji je druhá kontrola, na kterou nasazení nečeká.
- **Pevné počty řádků se rozbily.** První kontrola „bez stopy“ porovnávala s pevnými čísly. Další legitimní migrace přidala řádek oprávnění a kontrola zčervenala. Teď porovnává počty na konci s počty na začátku.
- **Přeskočeno neznamená prošlo.** End-to-end kontroly potřebují lokální edge runtime. Když neběží, přeskočí se, a přeskočení se snadno přečte jako zelená.
- **Na vytíženém notebooku je pomalé.** Průchody po rolích potřebovaly na zatíženém stroji delší timeouty.
- **Dokazuje to, co popisují sondy.** Políčko, pro které nikdo nenapsal sondu, se hlásí jako mezera v pokrytí, ne jako bezpečné.

## Jak to zopakovat ve vaší aplikaci na Supabase

1. Sepište každé oprávnění, které obrazovka dovolí uložit: role × oblast × akce. Ten seznam je specifikace, ne vaše politiky.
2. Ke každému políčku zapište skutečnou akci, kterou má řídit, a jak se projeví odmítnutí (žádné řádky, chyba, 403).
3. Postavte databázi, která odpovídá produkci, a vytvořte její otisk, aby odchylka schématu běh zastavila.
4. Spusťte každou akci jako přihlášený člen, s políčkem vypnutým a pak zapnutým, a porovnejte.
5. Schválně rozbijte jednu politiku a ověřte, že ověření zčervená.
6. Každé políčko, které nic nedělá, skryjte nebo napojte, a z ověření udělejte bránu před pushem.

Pokud vaše aplikace už RLS má, ale nejste si jistí, že dělá to, co říká obrazovka nastavení, stejná metoda funguje jako audit existujícího kódu, první krok [záchrany projektu](https://madsalt.tech/cs/services/project-rescue/). O projektu za těmito čísly jsem psal v [případové studii evidence času pro auditorskou firmu](https://madsalt.tech/cs/work/audit-firm-time-tracking/).

Nejste si jistí, že vaše RLS odpovídá obrazovce nastavení?

Rád porovnám zkušenosti s tím, jak to testujete. Bez nabídky.

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