---
title: "Supabase security definer view: por qué se salta RLS"
description: "Por qué una vista de Supabase devuelve filas que RLS oculta en su tabla, qué significa el aviso security_definer_view y el ajuste que lo corrige."
url: "https://madsalt.tech/es/notes/supabase-security-definer-view/"
author: "Denys Zaichenko"
author_url: "https://madsalt.tech/es/about/"
language: "es"
published: 2026-11-02
---

# Supabase security definer view: por qué se salta RLS

En resumen

Una vista de Postgres se ejecuta con los permisos de su propietario, salvo que se cree con `security_invoker = true`. En Supabase el propietario suele ser `postgres`, que se salta RLS, así que una vista sobre una tabla protegida devuelve filas que la propia tabla oculta. En mi stack local la clave pública obtuvo 0 filas de la tabla y 3 de 3 de una vista simple sobre ella; con `security_invoker = true` la vista también devolvió 0. En Postgres 15 y posteriores, actívelo en cada vista de un esquema expuesto.

Mi [verificador de seguridad de Supabase](https://madsalt.tech/es/tools/supabase-security-check/) gratuito tiene una regla llamada "Vistas que se saltan RLS", y el Security Advisor de Supabase informa de lo mismo como `security_definer_view`. Es fácil pasarlo por alto, porque la tabla de la que se filtran los datos parece protegida.

## ¿Por qué una vista de Supabase se salta RLS?

Una vista es una consulta guardada. Por defecto, Postgres comprueba los permisos y RLS de las tablas que contiene como el propietario de la vista, no como quien la llama. La [guía de RLS](https://supabase.com/docs/guides/database/postgres/row-level-security) de Supabase lo dice sin rodeos: las vistas se saltan RLS por defecto porque suelen crearse con el usuario `postgres`, y una vista sobre una tabla protegida entrega todas las filas que sus políticas debían retener.

La Data API sirve las vistas como si fueran tablas. Si la vista está en un esquema expuesto y `anon` o `authenticated` pueden hacer select sobre ella, la clave pública la lee.

## ¿Qué devolvió la vista en mi prueba?

En un stack local de Supabase (CLI 2.109.1, Postgres 17.6, PostgREST 14.14) creé una tabla `orders` con tres clientes ficticios, activé RLS sin ninguna política y creé una vista `order_summary` sobre ella con un grant de `select` para `anon`. Después llamé a ambas con la publishable key:

![Misma tabla, tres formas de leerla. Supabase local, RLS activado sin política, publishable key. La propia tabla: Publishable key → orders, RLS activado → 0 filas; Una vista simple sobre ella: Publishable key → vista order_summary → 3 de 3 filas; La vista con security_invoker: Publishable key → vista security_invoker → 0 filas.](https://madsalt.tech/charts/es/supabase-security-definer-view.svg)

*Mi reproducción en un stack local de Supabase, 23 de octubre de 2026.*

| Petición con la publishable key                             | Resultado                          |
| ----------------------------------------------------------- | ---------------------------------- |
| `GET /rest/v1/orders` (RLS activado, sin política)          | `[]`                               |
| `GET /rest/v1/order_summary` (vista simple)                 | las 3 filas, con nombres y correos |
| `GET /rest/v1/order_summary` tras `security_invoker = true` | `[]`                               |

Misma tabla, misma clave. Solo el ajuste de la vista cambió lo que se devolvió.

## ¿Cómo se corrige?

En Postgres 15 y posteriores, que es lo que ejecuta hoy cualquier proyecto de Supabase, haga que la vista compruebe RLS como quien la llama:

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

En una vista nueva, póngalo en la definición:

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

Entonces quien llama también necesita `select` sobre la tabla subyacente, y las políticas de la tabla deciden las filas. Si una vista debe agregar datos que quien la llama no puede ver fila por fila, como un recuento público, déjela fuera del esquema expuesto y sírvala mediante una función con un resultado acotado.

Para listar las vistas de `public` que todavía se ejecutan como su propietario:

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

En mi stack, con una vista simple y dos con `security_invoker = true`, devolvió solo la simple.

Las vistas materializadas no admiten RLS en absoluto, así que el verificador las señala aparte en la API, bajo "Vistas materializadas o tablas foráneas en la API". Qué más esquiva las políticas, desde la secret key hasta las funciones `SECURITY DEFINER`, está en mi [lista de seguridad para apps Lovable](https://madsalt.tech/es/blog/lovable-app-security-checklist/), y cómo demostrar las propias políticas está en [cómo probar Supabase RLS](https://madsalt.tech/es/blog/how-to-test-supabase-rls/). Una política que busca equipos mediante una función auxiliar, sin recursión, está en [Políticas RLS de Supabase: ejemplos probados con la API](https://madsalt.tech/es/blog/supabase-rls-policy-examples/). Una función security definer necesita un ajuste más, un `search_path` fijo; por qué `search_path = public` no bastó en mi prueba está en [Supabase "function search path mutable": qué significa](https://madsalt.tech/es/notes/supabase-function-search-path-mutable/).
