---
title: "Supabase security definer view: porque ignora o RLS"
description: "Porque é que uma vista do Supabase devolve linhas que o RLS esconde na tabela, o que significa o aviso security_definer_view e a definição que o corrige."
url: "https://madsalt.tech/pt/notes/supabase-security-definer-view/"
author: "Denys Zaichenko"
author_url: "https://madsalt.tech/pt/about/"
language: "pt"
published: 2026-11-02
---

# Supabase security definer view: porque ignora o RLS

Em resumo

Uma vista do Postgres corre com os direitos do seu dono, a menos que seja criada com `security_invoker = true`. No Supabase o dono costuma ser o `postgres`, que ignora o RLS, por isso uma vista sobre uma tabela protegida devolve linhas que a própria tabela esconde. Na minha stack local a chave pública obteve 0 linhas da tabela e 3 de 3 de uma vista simples sobre ela; com `security_invoker = true` a vista também devolveu 0. No Postgres 15 e posteriores, ative-o em todas as vistas de um schema exposto.

O meu [verificador de segurança do Supabase](https://madsalt.tech/pt/tools/supabase-security-check/) gratuito tem uma regra chamada "Vistas que contornam o RLS", e o Security Advisor do Supabase reporta o mesmo como `security_definer_view`. É fácil passar-lhe ao lado, porque a tabela de onde os dados saem parece protegida.

## Porque é que uma vista do Supabase ignora o RLS?

Uma vista é uma consulta guardada. Por omissão, o Postgres verifica as permissões e o RLS das tabelas lá dentro como o dono da vista, não como quem a chama. O [guia de RLS](https://supabase.com/docs/guides/database/postgres/row-level-security) do Supabase di-lo sem rodeios: as vistas ignoram o RLS por omissão porque costumam ser criadas com o utilizador `postgres`, e uma vista sobre uma tabela protegida entrega todas as linhas que as suas políticas deviam reter.

A Data API serve as vistas como se fossem tabelas. Se a vista está num schema exposto e o `anon` ou o `authenticated` podem fazer select nela, a chave pública lê-a.

## O que devolveu a vista no meu teste?

Numa stack local do Supabase (CLI 2.109.1, Postgres 17.6, PostgREST 14.14) criei uma tabela `orders` com três clientes fictícios, ativei o RLS sem política nenhuma e criei uma vista `order_summary` sobre ela com um grant de `select` para o `anon`. Depois chamei as duas com a publishable key:

![A mesma tabela, três formas de a ler. Supabase local, RLS ligado sem política, publishable key. A própria tabela: Publishable key → orders, RLS ligado → 0 linhas; Uma vista simples sobre ela: Publishable key → vista order_summary → 3 de 3 linhas; A vista com security_invoker: Publishable key → vista security_invoker → 0 linhas.](https://madsalt.tech/charts/pt/supabase-security-definer-view.svg)

*A minha reprodução numa stack local do Supabase, 23 de outubro de 2026.*

| Pedido com a publishable key                                     | Resultado                       |
| ---------------------------------------------------------------- | ------------------------------- |
| `GET /rest/v1/orders` (RLS ligado, sem política)                 | `[]`                            |
| `GET /rest/v1/order_summary` (vista simples)                     | as 3 linhas, com nomes e emails |
| `GET /rest/v1/order_summary` depois de `security_invoker = true` | `[]`                            |

A mesma tabela, a mesma chave. Só a definição da vista mudou o que voltou.

## Como se corrige?

No Postgres 15 e posteriores, que é o que qualquer projeto Supabase atual usa, faça a vista verificar o RLS como quem a chama:

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

Numa vista nova, ponha-o na definição:

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

Quem chama passa então a precisar também de `select` na tabela subjacente, e as políticas da tabela decidem as linhas. Se uma vista tiver de agregar dados que quem chama não pode ver linha a linha, como uma contagem pública, mantenha-a fora do schema exposto e sirva-a através de uma função com um resultado restrito.

Para listar as vistas de `public` que ainda correm como o seu dono:

```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 minha stack, com uma vista simples e duas com `security_invoker = true`, devolveu só a simples.

As vistas materializadas não suportam RLS de todo, por isso o verificador assinala-as à parte na API, em "Vistas materializadas ou tabelas externas na API". O que mais contorna as políticas, da secret key às funções `SECURITY DEFINER`, está na minha [lista de segurança para apps Lovable](https://madsalt.tech/pt/blog/lovable-app-security-checklist/), e como provar as próprias políticas está em [como testar o Supabase RLS](https://madsalt.tech/pt/blog/how-to-test-supabase-rls/). Uma policy que procura equipas através de uma função auxiliar, sem recursão, está em [Exemplos de RLS policy no Supabase, testados pela API](https://madsalt.tech/pt/blog/supabase-rls-policy-examples/). Uma função security definer precisa de mais uma definição, um `search_path` fixo; porque `search_path = public` não bastou no meu teste está em [Supabase "function search path mutable": o que significa](https://madsalt.tech/pt/notes/supabase-function-search-path-mutable/).
