---
title: "Supabase security definer view: perché aggira la RLS"
description: "Perché una vista Supabase restituisce righe che la RLS nasconde sulla sua tabella, cosa significa l'avviso security_definer_view e come si risolve."
url: "https://madsalt.tech/it/notes/supabase-security-definer-view/"
author: "Denys Zaichenko"
author_url: "https://madsalt.tech/it/about/"
language: "it"
published: 2026-11-02
---

# Supabase security definer view: perché aggira la RLS

In breve

Una vista Postgres gira con i diritti del suo proprietario, a meno che non sia creata con `security_invoker = true`. In Supabase il proprietario è di solito `postgres`, che aggira la RLS: una vista su una tabella protetta restituisce quindi righe che la tabella stessa nasconde. Sul mio stack locale la chiave pubblica ha ottenuto 0 righe dalla tabella e 3 su 3 da una vista semplice sopra di essa; con `security_invoker = true` anche la vista ne ha restituite 0. Da Postgres 15 in poi, lo imposti su ogni vista di uno schema esposto.

Il mio [checker di sicurezza per Supabase](https://madsalt.tech/it/tools/supabase-security-check/) gratuito ha una regola chiamata "View che aggirano RLS", e il Security Advisor di Supabase segnala la stessa cosa come `security_definer_view`. È facile non vederla, perché la tabella da cui perde sembra protetta.

## Perché una vista Supabase aggira la RLS?

Una vista è una query salvata. Di default Postgres controlla permessi e RLS delle tabelle al suo interno come proprietario della vista, non come la persona che la chiama. La [guida alla RLS](https://supabase.com/docs/guides/database/postgres/row-level-security) di Supabase lo dice senza giri di parole: le viste aggirano la RLS di default perché di solito sono create con l'utente `postgres`, e una vista su una tabella protetta consegna ogni riga che le sue policy dovevano trattenere.

La Data API serve le viste come tabelle. Se la vista è in uno schema esposto e `anon` o `authenticated` possono leggerla, la legge la chiave pubblica.

## Cosa restituiva la vista nel mio test?

Su uno stack Supabase locale (CLI 2.109.1, Postgres 17.6, PostgREST 14.14) ho creato una tabella `orders` con tre clienti finti, attivato la RLS senza policy e creato sopra una vista `order_summary` con un grant `select` per `anon`. Poi ho chiamato entrambe con la publishable key:

![Stessa tabella, tre modi di leggerla. Supabase locale, RLS attiva senza policy, publishable key. La tabella stessa: Publishable key → orders, RLS attiva → 0 righe; Una vista semplice sopra di essa: Publishable key → vista order_summary → 3 righe su 3; La vista con security_invoker: Publishable key → vista security_invoker → 0 righe.](https://madsalt.tech/charts/it/supabase-security-definer-view.svg)

*La mia riproduzione su uno stack Supabase locale, 23 ottobre 2026.*

| Richiesta con la publishable key                            | Risultato                           |
| ----------------------------------------------------------- | ----------------------------------- |
| `GET /rest/v1/orders` (RLS attiva, nessuna policy)          | `[]`                                |
| `GET /rest/v1/order_summary` (vista semplice)               | tutte le 3 righe, con nomi ed email |
| `GET /rest/v1/order_summary` dopo `security_invoker = true` | `[]`                                |

Stessa tabella, stessa chiave. Solo l'impostazione della vista ha cambiato ciò che tornava.

## Come si risolve?

Da Postgres 15 in poi, su cui gira ogni progetto Supabase attuale, faccia controllare la RLS alla vista come chi la chiama:

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

Per una nuova vista lo metta nella definizione:

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

Chi chiama deve allora avere anche `select` sulla tabella sottostante, e sono le policy della tabella a decidere le righe. Se una vista deve aggregare dati che chi chiama non può vedere riga per riga, come un conteggio pubblico, la tenga fuori dallo schema esposto e la serva invece tramite una funzione con un risultato ristretto.

Per elencare le viste di `public` che girano ancora come il loro proprietario:

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

Sul mio stack, con una vista semplice e due con `security_invoker = true`, ha restituito solo quella semplice.

Le viste materializzate non supportano affatto la RLS, quindi il checker le segnala a parte, sotto "Viste materializzate o foreign table nell’API". Tutto il resto di ciò che passa oltre le policy, dalla secret key alle funzioni `SECURITY DEFINER`, è nella mia [checklist di sicurezza per le app Lovable](https://madsalt.tech/it/blog/lovable-app-security-checklist/), e come dimostrare le policy stesse è in [come testare la Supabase RLS](https://madsalt.tech/it/blog/how-to-test-supabase-rls/). Una policy che cerca i team tramite una funzione di supporto, senza ricorsione, è in [Esempi di Supabase RLS policy, testati tramite API](https://madsalt.tech/it/blog/supabase-rls-policy-examples/). Una funzione security definer richiede un'altra impostazione, un `search_path` fisso; perché `search_path = public` non bastava nel mio test è in [Supabase Function Search Path Mutable: cosa significa](https://madsalt.tech/it/notes/supabase-function-search-path-mutable/).
