---
title: "Supabase view security definer: proč obchází RLS"
description: "Proč view v Supabase vrací řádky, které RLS na tabulce skrývá, co znamená varování Advisoru security_definer_view a jedno nastavení, které to opraví."
url: "https://madsalt.tech/cs/notes/supabase-security-definer-view/"
author: "Denys Zaichenko"
author_url: "https://madsalt.tech/cs/about/"
language: "cs"
published: 2026-11-02
---

# Supabase view security definer: proč obchází RLS

TL;DR

View v Postgresu běží s právy svého vlastníka, pokud není vytvořeno s `security_invoker = true`. V Supabase je vlastníkem obvykle `postgres`, který RLS obchází, takže view nad chráněnou tabulkou vrací řádky, které tabulka sama skrývá. Na mém lokálním stacku dostal veřejný klíč z tabulky 0 řádků a z obyčejného view nad ní všechny 3 ze 3; s `security_invoker = true` vrátilo view také 0. Na Postgresu 15 a novějším ho nastavte na každém view ve vystaveném schématu.

Můj bezplatný [bezpečnostní checker pro Supabase](https://madsalt.tech/cs/tools/supabase-security-check/) má pravidlo „Pohledy, které obcházejí RLS“ a Security Advisor od Supabase hlásí totéž jako `security_definer_view`. Snadno se přehlédne, protože tabulka, ze které data unikají, vypadá chráněně.

## Proč view v Supabase obchází RLS?

View je uložený dotaz. Ve výchozím stavu Postgres kontroluje oprávnění a RLS na tabulkách uvnitř něj jako vlastník view, ne jako ten, kdo ho volá. [Průvodce RLS](https://supabase.com/docs/guides/database/postgres/row-level-security) od Supabase to říká přímo: views ve výchozím stavu obcházejí RLS, protože se obvykle vytvářejí uživatelem `postgres`, a view nad chráněnou tabulkou vydá každý řádek, který její politiky měly zadržet.

Data API servíruje views stejně jako tabulky. Je-li view ve vystaveném schématu a `anon` nebo `authenticated` z něj smí vybírat, přečte ho veřejný klíč.

## Co view vrátilo v mém testu?

Na lokálním stacku Supabase (CLI 2.109.1, Postgres 17.6, PostgREST 14.14) jsem vytvořil tabulku `orders` se třemi fiktivními zákazníky, zapnul RLS bez politiky a nad ní view `order_summary` s grantem `select` pro `anon`. Pak jsem obojí zavolal s publishable key:

![Stejná tabulka, tři způsoby čtení. Lokální Supabase, RLS zapnuté bez politiky, publishable key. Samotná tabulka: Publishable key → orders, RLS zapnuté → 0 řádků; Obyčejné view nad ní: Publishable key → view order_summary → 3 ze 3 řádků; View se security_invoker: Publishable key → view security_invoker → 0 řádků.](https://madsalt.tech/charts/cs/supabase-security-definer-view.svg)

*Moje reprodukce na lokálním Supabase, 23. října 2026.*

| Požadavek s publishable key                               | Výsledek                            |
| --------------------------------------------------------- | ----------------------------------- |
| `GET /rest/v1/orders` (RLS zapnuté, bez politiky)         | `[]`                                |
| `GET /rest/v1/order_summary` (obyčejné view)              | všechny 3 řádky, se jmény a e-maily |
| `GET /rest/v1/order_summary` po `security_invoker = true` | `[]`                                |

Stejná tabulka, stejný klíč. Odpověď změnilo jen nastavení view.

## Jak to opravit?

Na Postgresu 15 a novějším, který běží na každém aktuálním projektu Supabase, nechte view kontrolovat RLS jako volající:

```sql
alter view public.order_summary set (security_invoker = true);
```

U nového view ho uveďte v definici:

```sql
create view public.order_summary
with (security_invoker = true)
as select customer, email, total from public.orders;
```

Volající pak potřebuje také `select` na podkladovou tabulku a o řádcích rozhodují politiky tabulky. Pokud view musí agregovat data, která volající po řádcích vidět nesmí, například veřejný počet, nechte ho mimo vystavené schéma a nabídněte ho přes funkci s úzkým výsledkem.

Seznam views v `public`, které stále běží jako jejich vlastník:

```sql
select c.relname as view
from pg_class c join pg_namespace n on n.oid = c.relnamespace
where n.nspname = 'public' and c.relkind = 'v'
  and not coalesce(
    (select bool_or(option = 'security_invoker=true' or option = 'security_invoker=on')
     from unnest(c.reloptions) as option), false);
```

Na mém stacku, s jedním obyčejným view a dvěma se `security_invoker = true`, vrátil jen to obyčejné.

Materializované views RLS nepodporují vůbec, proto je checker hlásí v API zvlášť, pod „Materializované pohledy nebo cizí tabulky v API“. Co dalšího obchází politiky, od secret key po funkce `SECURITY DEFINER`, je v mém [checklistu zabezpečení aplikace z Lovable](https://madsalt.tech/cs/blog/lovable-app-security-checklist/) a jak dokázat, že samotné politiky fungují, ukazuje [jak testuji Supabase RLS](https://madsalt.tech/cs/blog/how-to-test-supabase-rls/). Politika, která vyhledává týmy přes pomocnou funkci, bez rekurze, je v článku [Supabase RLS policy: příklady otestované přes API](https://madsalt.tech/cs/blog/supabase-rls-policy-examples/). Funkce se `SECURITY DEFINER` potřebuje ještě jedno nastavení, pevný `search_path`; proč v mém testu `search_path = public` nestačilo, je v poznámce [Supabase „function search path mutable“: co to znamená](https://madsalt.tech/cs/notes/supabase-function-search-path-mutable/).
