Saltar al contenido

Fundamentos de seguridad

Supabase "permission denied for table": el grant que falta

Desde el 30 de octubre, una tabla nueva de Supabase responde "permission denied for table" hasta que le das acceso. El grant del email es la mitad del arreglo.

Vlad Tkachenko10 min de lectura
Una hilera de puertas a tablas de una base de datos. Las más antiguas se abren a filas iluminadas y la más nueva, al final, está cerrada.

En resumen

  • Desde el 30 de octubre de 2026, una tabla nueva de tu proyecto de Supabase responde "permission denied for table" hasta que alguien le da acceso. Las tablas que ya tienes siguen funcionando exactamente igual que hoy.
  • Un grant decide si una petición llega a una tabla. Row Level Security sigue decidiendo qué filas se lleva. Dar a anon acceso a una tabla sin reglas deja cada fila legible para cualquiera.
  • El cambio no cierra nada que ya esté abierto. Comprueba qué puede leer hoy un desconocido, y otra vez después de cada tabla que añadas.

Si tu app corre sobre Supabase, seguramente te ha llegado un email sobre el 30 de octubre. Dice que nada cambia para las tablas que ya tienes, y a continuación te da tres sentencias de SQL. O ya es noviembre: le pediste a Lovable, Bolt o Cursor una función nueva, la pantalla nueva salió vacía, y en algún lugar de la consola hay un mensaje que dice permission denied for table. Las dos cosas son el mismo cambio de Supabase, visto desde cada lado de la fecha.

Aquí está lo que el email se deja fuera: el SQL que te enseña es la mitad del arreglo. Ejecuta esa mitad sola, en la tabla equivocada, y tu app vuelve a funcionar mientras la tabla nueva entrega sus filas a cualquiera que las pida.

¿Qué significa "permission denied for table" en Supabase?

Significa que el tipo de visitante que hace la petición no tiene ningún acceso a esa tabla, así que la base de datos la rechazó antes de mirar una sola fila.

Supabase clasifica cada petición en un rol, que es el tipo de visitante del que viene. anon es alguien que no ha iniciado sesión. authenticated es alguien que sí. service_role es tu propio código de servidor usando la clave secreta. Un grant es la sentencia que da a uno de esos roles acceso a una tabla en Postgres, la base de datos sobre la que corre Supabase. Sin grant no hay acceso, escribas lo que escribas en otro sitio.

Cuando falta el grant, Supabase responde así, normalmente como un 401 o un 403:

{
  "code": "42501",
  "message": "permission denied for table comments",
  "hint": "Grant the required privileges to the current role with: GRANT SELECT ON public.comments TO anon;"
}

La pista es la parte útil. Nombra el rol que fue rechazado y la sentencia exacta que lo dejaría pasar.

Piensa en cada tabla como una habitación. El grant es la puerta, y hay una puerta distinta para cada rol. Row Level Security, las reglas por fila que quizá ya conozcas, decide qué cajones puede abrir un visitante una vez dentro. "Permission denied for table" significa que alguien está delante de una puerta cerrada. Nunca llegó hasta tus reglas.

¿Qué cambia el 30 de octubre?

Las tablas nuevas del esquema public, la carpeta de tablas que usa tu builder salvo que le digas otra cosa, empiezan a llegar con las puertas cerradas.

Hasta ahora, Supabase abría automáticamente las tres puertas de cada tabla nueva: leer, añadir, cambiar y borrar, para anon, authenticated y service_role por igual. Una tabla era alcanzable desde tu app en cuanto existía, y tus reglas de Row Level Security eran lo único entre un desconocido y sus filas. El changelog de Supabase da el motivo sin rodeos: los agentes y las plataformas de IA crean ahora tablas sin que nadie revise el cambio, y los grants automáticos exponían tablas que "a developer forgot to protect", que alguien olvidó proteger.

FechaQué pasó o qué pasa
28 de abril de 2026Los proyectos nuevos podían renunciar a los grants automáticos al crearse.
30 de mayo de 2026Empezó a desplegarse "sin grants automáticos" como opción por defecto en nuevos.
30 de octubre de 2026Los proyectos existentes también dejan de recibirlos.

Si tu proyecto se creó después de finales de mayo, puede que ya funcione así, porque Supabase fue desplegando el nuevo valor por defecto en los proyectos nuevos durante las semanas siguientes a esa fecha.

Las tablas que existen el 30 de octubre conservan las puertas que tienen, incluida una que estaba abierta a desconocidos. Solo las tablas creadas después empiezan con la puerta cerrada.

Tres detalles que conviene saber antes de la fecha:

  • Solo el esquema public. Storage y el inicio de sesión guardan sus tablas en esquemas propios, storage y auth, y Supabase dice que sus grants y sus valores por defecto se quedan como están.
  • La clave secreta también se rechaza. Los grants automáticos cubrían también a service_role, así que una edge function (código de servidor que Supabase ejecuta por ti) que use tu clave secreta recibe el mismo error en una tabla nueva hasta que service_role tenga un grant propio.
  • Una tabla borrada y creada de nuevo es una tabla nueva. Los grants pertenecen a la propia tabla y se van con ella cuando se borra. Si tu builder reconstruye una tabla para cambiarla, la reconstruida empieza con la puerta cerrada.

¿Se romperá mi app el 30 de octubre?

Ese día no. Se rompe la primera vez que algo crea una tabla nueva sin crear también el grant.

Para la mayoría de quienes leen esto, ese algo es su builder. Pides una sección de comentarios. El builder escribe una migración, que es el archivo de cambios de base de datos que ejecuta por ti, y la migración crea una tabla comments. Si también escribe los grants, nunca verás este error. Si no, la pantalla de comentarios no muestra nada, o guardar un comentario falla, o aparece un mensaje en rojo, según cómo gestione tu app un error. Todas las pantallas que ya tenías siguen funcionando.

Supabase publica una agent skill para herramientas de programación con IA que incluye el paso del grant. Que tu builder la use depende de tu builder, así que lo prudente es suponer que la próxima tabla que cree puede llegar con la puerta cerrada.

¿Es seguro ejecutar el GRANT que sugiere el error?

Solo cuando la tabla ya tiene Row Level Security activado y una regla escrita. El grant decide quién cruza la puerta. Nada en él decide qué cajones abre.

La clave con la que tu app hace sus peticiones a Supabase viaja dentro de tu app, así que un grant a anon significa que cualquier visitante, con sesión o sin ella, puede pedirle filas a esa tabla. Con una regla puesta, recibe las filas que la regla permite. Con Row Level Security apagado, recibe todas las filas de la tabla, y nada en tu app se verá distinto.

El email enseña tres grants y ahí se queda. El propio changelog de Supabase enseña tres pasos, y dice que hay que tratarlos como una unidad ("treat these three steps as a unit"):

-- 1. quién puede llegar a la tabla
grant select on public.orders to authenticated;
grant select, insert, update, delete on public.orders to service_role;

-- 2. activar las reglas por fila
alter table public.orders enable row level security;

-- 3. la regla en sí
create policy "Customers read their own orders"
  on public.orders
  for select
  to authenticated
  using (auth.uid() = user_id);

En ese ejemplo no hay ninguna línea para anon, y es a propósito. Nadie sin sesión debería llegar a una tabla de pedidos, así que la puerta para desconocidos se queda cerrada, y los clientes con sesión reciben solo la lectura que necesita su pantalla. Después la regla la reduce a sus propias filas. El consejo de Supabase es dar a cada rol el mínimo que necesita. Un grant a anon tiene sentido en una tabla cuyas filas son para todo el mundo, como los comentarios bajo una publicación pública.

El grant decide si una petición llega a la tabla. Las reglas que hay detrás deciden cuánto se lleva. Una puerta abierta sin reglas detrás entrega la tabla entera.

Si quieres ver en qué lado de ese dibujo están tus tablas, nuestro escaneo gratuito le hace a tu app en producción la pregunta que haría un desconocido, cuenta las filas que recibiría sin descargar ninguna, y tarda unos 20 segundos sin cuenta: escanea tu app.

Por qué los arreglos de Row Level Security no tocan este error

Porque el grant se comprueba primero. Una petición rechazada en la puerta nunca llega a tus reglas, así que cambiar las reglas no cambia nada del error.

Esto importa porque el código es compartido. 42501 es también lo que devuelve Postgres cuando Row Level Security rechaza un guardado, como en new row violates row-level security policy. Un asistente que se guíe solo por el código puede recurrir a los arreglos de ese otro error: apagar Row Level Security, o añadir una regla using (true), que deja pasar a todo el mundo. Ninguno de los dos hace desaparecer este error. Los dos se quedan ahí cuando por fin entra el grant, y entonces la puerta está abierta a unos cajones sin cerradura.

Las palabras del mensaje distinguen los dos casos, aunque el número no lo haga:

El mensaje diceQué cerradura rechazóQué lo arregla
permission denied for tablela puerta (el grant)un grant para el rol que nombra la pista
new row violates row-level security policylas reglasuna policy que permita esa fila
ningún error, y la lista sale vacíalas reglasuna policy de lectura para ese rol

Lo que el 30 de octubre no arregla

Ninguna tabla que ya tengas. Cada una conserva exactamente el acceso que tiene hoy, incluida una tabla que un desconocido puede leer ahora mismo.

El cambio va de tablas que aún no existen. Hasta ahora cada tabla nueva llegaba con las puertas abiertas, así que una app construida antes del 30 de octubre puede tener una en la que nadie puso nunca las cerraduras: Row Level Security nunca activado, o activado con una regla que deja entrar a todo el mundo. El 30 de octubre deja esas habitaciones exactamente como están.

Tres sitios te dicen dónde estás:

  1. La página de ajustes de Data API, que el email enlaza. En el panel está en Integrations y luego Data API, y muestra cuáles de tus tablas son alcanzables siquiera.
  2. El Security Advisor, que según Supabase muestra las tablas que conviene revisar antes del cambio.
  3. La vista desde fuera. Ninguno de los dos anteriores te dice qué recibe un desconocido. Para eso le preguntas a tu app en producción como lo haría un desconocido, que es lo que hace nuestro escaneo.

Cómo vigila Reeve las tablas que añades

Después del 30 de octubre, cada tabla que añada tu builder es una decisión nueva sobre quién cruza la puerta. Reeve comprueba la respuesta desde fuera, y Monitor la sigue comprobando.

  • El escaneo gratuito pregunta a cada tabla que nombra tu app cuántas filas recibiría un visitante sin sesión, lee el recuento y se detiene ahí sin descargar ninguna fila. Unos 20 segundos, sin cuenta: escanea tu app.
  • Reeve Monitor pasa las nueve comprobaciones cada hora en hasta tres apps y te manda un email el día en que tu nota empeora, así que un despliegue que abrió algo no espera a que vayas tú a mirar.
  • Care pasa las mismas comprobaciones y guarda una copia cifrada de tu base de datos de Supabase fuera de tu cuenta de Supabase, así que el arreglo de un asistente que reconstruye una tabla tiene una copia a la que volver. Cómo se hace la copia y cómo se restaura.

Lo que cubre cada plan está en la página de precios.

Qué hacer antes del 30 de octubre

Qué hacer

  • Deja en paz las tablas que ya tienes si lo único que quieres es que tu app siga funcionando. Conservan sus grants.
  • Pide a tu builder que escriba el grant, enable row level security y una policy en la misma migración que cada tabla nueva, como un solo cambio.
  • Cuando aparezca el error, lee qué rol nombra la pista. anon significa cualquier visitante de tu app.
  • No des nada a anon en una tabla hasta que Row Level Security esté activado y haya una regla escrita. Las tablas con personas o pedidos normalmente no necesitan ningún grant a anon.
  • Rechaza cualquier arreglo que apague Row Level Security o añada using (true) para quitar un error de permisos. Ninguno de los dos lo toca.
  • Comprueba qué puede leer un desconocido de las tablas que ya tienes. El 30 de octubre las deja como están.

Empieza por la tabla que un desconocido más querría leer, normalmente la que tiene personas dentro, y escanea tu app para ver qué entrega hoy.

Preguntas frecuentes

¿Mi app de Supabase dejará de funcionar el 30 de octubre?

No. Cada tabla que exista el 30 de octubre conserva el acceso que tiene, y Supabase ha confirmado que esos grants no se van a retirar. Lo que cambia es la siguiente tabla que se cree después de esa fecha. Nace inalcanzable desde tu app hasta que alguien le da acceso, así que lo primero que se rompe es la primera función nueva que necesite una tabla nueva.

¿Qué significa "permission denied for table" en Supabase?

Significa que el tipo de visitante que hace la petición no tiene ningún acceso a esa tabla, así que tu base de datos la rechazó antes de mirar una sola fila. Viene de Postgres, la base de datos sobre la que corre Supabase, con el código 42501, y suele llegar a tu app como un 401 o un 403. Supabase añade una pista con la sentencia GRANT exacta que dejaría pasar a ese visitante.

¿Es seguro ejecutar el GRANT que sugiere el error?

Solo cuando la tabla ya tiene Row Level Security activado y una regla escrita. Un grant a anon deja que cualquier visitante de tu app pida filas a la tabla, porque la clave que hace esas peticiones viaja dentro de tu app. Con una regla puesta, recibe las filas que la regla permite. Sin regla y con Row Level Security apagado, las recibe todas.

¿Este cambio afecta a Storage, al inicio de sesión o a mis edge functions?

Storage y el inicio de sesión viven en sus propios esquemas, storage y auth, y Supabase dice que sus grants y sus valores por defecto se quedan como están. Las edge functions son otra historia. Una que use tu clave secreta sigue necesitando un grant en cualquier tabla nueva, porque los grants automáticos que desaparecen cubrían a service_role además de los dos roles que usan tus visitantes.

¿Puedo volver a activar el comportamiento anterior?

Supabase documenta cómo hacerlo, con un ajuste en la página Data API del panel o con SQL, y lo desaconseja. Con eso activado, cada tabla nueva es alcanzable por cualquier visitante desde el momento en que existe, y Row Level Security es lo único que se interpone entre un desconocido y sus filas. Así funcionaban todos los proyectos antes del cambio.

Escrito por

Vlad Tkachenko

Fundador de Reeve

Me paso el día mirando apps creadas con Lovable, Bolt, v0, Cursor y Replit, y la corta lista de errores que aparecen en ellas una y otra vez.

Más sobre el autor

Sigue leyendo

Todos los artículos

¿No sabes cómo está tu propia app?

Haz un escaneo gratuito y obtén una nota clara de la A a la F en unos 20 segundos. Sin cuenta y sin tarjeta.

Analizar mi app gratis

Comprobación externa automatizada, no una auditoría completa. La ausencia de hallazgos no es garantía de seguridad.